Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a862a07ae1 | ||
|
|
4d63c1bea4 | ||
|
|
91c38de167 | ||
|
|
926afc82b5 | ||
|
|
4a066d71f4 | ||
|
|
7fc611ed08 | ||
|
|
924d8c3767 | ||
|
|
67ff1f73cd | ||
|
|
0ad247bae3 | ||
|
|
2de2885f2f | ||
|
|
069c3837f3 | ||
|
|
8a88565772 | ||
|
|
e7b32be1e0 | ||
|
|
d4640d7617 | ||
|
|
29b20bc5b0 | ||
|
|
071adb5ecb |
@@ -11,7 +11,7 @@ import (
|
||||
type AuthorityRepository struct{ pool *pgxpool.Pool }
|
||||
|
||||
func OpenAuthority(ctx context.Context, c ContinuityConfig) (*AuthorityRepository, error) {
|
||||
p, e := pgxpool.New(ctx, fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s", c.Host, c.Port, c.Database, c.User, c.Password))
|
||||
p, e := openPool(ctx, c)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
@@ -19,8 +19,7 @@ var ErrCapabilityImplementationNotFound = errors.New("capability implementation
|
||||
type CapabilityRepository struct{ pool *pgxpool.Pool }
|
||||
|
||||
func OpenCapability(ctx context.Context, config ContinuityConfig) (*CapabilityRepository, error) {
|
||||
dsn := fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s", config.Host, config.Port, config.Database, config.User, config.Password)
|
||||
pool, err := pgxpool.New(ctx, dsn)
|
||||
pool, err := openPool(ctx, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -218,6 +217,81 @@ func (r *CapabilityRepository) SaveCapabilityUsageObservation(ctx context.Contex
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *CapabilityRepository) SaveSkillProfile(ctx context.Context, value capability.SkillProfile) error {
|
||||
tx, err := r.pool.Begin(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tx.Rollback(ctx) }()
|
||||
_, err = tx.Exec(ctx, `INSERT INTO capability.skill_profiles (
|
||||
skill_profile_id,revision,name,description,applicable_context,implementation_ref,implementation_version,status,
|
||||
supporting_experience_refs,known_failure_experience_refs,confidence_millis,last_validated_unix_millis,
|
||||
observed_failure_count,observed_correction_count,recorded_at_unix_millis)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15) ON CONFLICT DO NOTHING`,
|
||||
value.ID, value.Revision, value.Name, value.Description, value.ApplicableContext, value.ImplementationRef,
|
||||
value.ImplementationVersion, value.Status, value.SupportingExperienceRefs, value.KnownFailureExperienceRefs,
|
||||
value.ConfidenceMillis, value.LastValidatedUnixMillis, value.ObservedFailureCount, value.ObservedCorrectionCount,
|
||||
value.RecordedAtUnixMillis)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = tx.Exec(ctx, `INSERT INTO capability.skill_profile_heads(skill_profile_id,revision,status)
|
||||
VALUES ($1,$2,$3)
|
||||
ON CONFLICT (skill_profile_id) DO UPDATE SET revision=EXCLUDED.revision,status=EXCLUDED.status,updated_at=CURRENT_TIMESTAMP
|
||||
WHERE capability.skill_profile_heads.revision < EXCLUDED.revision`, value.ID, value.Revision, value.Status)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
|
||||
func (r *CapabilityRepository) LoadSkillProfile(ctx context.Context, id capability.SkillProfileID) (capability.SkillProfile, error) {
|
||||
var value capability.SkillProfile
|
||||
err := r.pool.QueryRow(ctx, `SELECT
|
||||
p.skill_profile_id,p.revision,p.name,p.description,p.applicable_context,p.implementation_ref,p.implementation_version,p.status,
|
||||
p.supporting_experience_refs,p.known_failure_experience_refs,p.confidence_millis,p.last_validated_unix_millis,
|
||||
p.observed_failure_count,p.observed_correction_count,p.recorded_at_unix_millis
|
||||
FROM capability.skill_profile_heads h
|
||||
JOIN capability.skill_profiles p ON p.skill_profile_id=h.skill_profile_id AND p.revision=h.revision
|
||||
WHERE p.skill_profile_id=$1`, id).Scan(&value.ID, &value.Revision, &value.Name, &value.Description, &value.ApplicableContext,
|
||||
&value.ImplementationRef, &value.ImplementationVersion, &value.Status, &value.SupportingExperienceRefs,
|
||||
&value.KnownFailureExperienceRefs, &value.ConfidenceMillis, &value.LastValidatedUnixMillis,
|
||||
&value.ObservedFailureCount, &value.ObservedCorrectionCount, &value.RecordedAtUnixMillis)
|
||||
if err != nil {
|
||||
return capability.SkillProfile{}, fmt.Errorf("load skill profile: %w", err)
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (r *CapabilityRepository) RecentSkillProfiles(ctx context.Context, limit int) ([]capability.SkillProfile, error) {
|
||||
if limit < 1 || limit > 100 {
|
||||
return nil, fmt.Errorf("invalid skill profile limit")
|
||||
}
|
||||
rows, err := r.pool.Query(ctx, `SELECT
|
||||
p.skill_profile_id,p.revision,p.name,p.description,p.applicable_context,p.implementation_ref,p.implementation_version,p.status,
|
||||
p.supporting_experience_refs,p.known_failure_experience_refs,p.confidence_millis,p.last_validated_unix_millis,
|
||||
p.observed_failure_count,p.observed_correction_count,p.recorded_at_unix_millis
|
||||
FROM capability.skill_profile_heads h
|
||||
JOIN capability.skill_profiles p ON p.skill_profile_id=h.skill_profile_id AND p.revision=h.revision
|
||||
ORDER BY h.updated_at DESC, p.skill_profile_id LIMIT $1`, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var result []capability.SkillProfile
|
||||
for rows.Next() {
|
||||
var value capability.SkillProfile
|
||||
if err := rows.Scan(&value.ID, &value.Revision, &value.Name, &value.Description, &value.ApplicableContext,
|
||||
&value.ImplementationRef, &value.ImplementationVersion, &value.Status, &value.SupportingExperienceRefs,
|
||||
&value.KnownFailureExperienceRefs, &value.ConfidenceMillis, &value.LastValidatedUnixMillis,
|
||||
&value.ObservedFailureCount, &value.ObservedCorrectionCount, &value.RecordedAtUnixMillis); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, value)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
func (r *CapabilityRepository) LoadBinding(ctx context.Context, id capability.CapabilityBindingID, revision int64) (capability.Binding, error) {
|
||||
var binding capability.Binding
|
||||
err := r.pool.QueryRow(ctx, `SELECT binding_id,capability_id,owner_principal_id,revision,state,https_origin,delivery_path,receipt_path,
|
||||
|
||||
@@ -23,7 +23,7 @@ var (
|
||||
type CommitRepository struct{ pool *pgxpool.Pool }
|
||||
|
||||
func OpenCommit(ctx context.Context, c ContinuityConfig) (*CommitRepository, error) {
|
||||
p, e := pgxpool.New(ctx, fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s", c.Host, c.Port, c.Database, c.User, c.Password))
|
||||
p, e := openPool(ctx, c)
|
||||
if e != nil {
|
||||
return nil, e
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/authority"
|
||||
"git.cin.su/m/Assistant/contracts/continuity"
|
||||
@@ -29,9 +30,25 @@ var (
|
||||
type ContinuityConfig struct{ Host, Port, Database, User, Password string }
|
||||
type ContinuityRepository struct{ pool *pgxpool.Pool }
|
||||
|
||||
func openPool(ctx context.Context, config ContinuityConfig) (*pgxpool.Pool, error) {
|
||||
poolConfig, err := pgxpool.ParseConfig("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
port, err := strconv.ParseUint(config.Port, 10, 16)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid postgres port: %w", err)
|
||||
}
|
||||
poolConfig.ConnConfig.Host = config.Host
|
||||
poolConfig.ConnConfig.Port = uint16(port)
|
||||
poolConfig.ConnConfig.Database = config.Database
|
||||
poolConfig.ConnConfig.User = config.User
|
||||
poolConfig.ConnConfig.Password = config.Password
|
||||
return pgxpool.NewWithConfig(ctx, poolConfig)
|
||||
}
|
||||
|
||||
func OpenContinuity(ctx context.Context, config ContinuityConfig) (*ContinuityRepository, error) {
|
||||
dsn := fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s", config.Host, config.Port, config.Database, config.User, config.Password)
|
||||
pool, err := pgxpool.New(ctx, dsn)
|
||||
pool, err := openPool(ctx, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type EpistemicsRepository struct{ pool *pgxpool.Pool }
|
||||
|
||||
func OpenEpistemics(ctx context.Context, config ContinuityConfig) (*EpistemicsRepository, error) {
|
||||
pool, err := openPool(ctx, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &EpistemicsRepository{pool: pool}, nil
|
||||
}
|
||||
|
||||
func (r *EpistemicsRepository) Close() { r.pool.Close() }
|
||||
|
||||
func (r *EpistemicsRepository) SaveSystemExpectation(ctx context.Context, value epistemics.SystemExpectation) error {
|
||||
_, err := r.pool.Exec(ctx, `INSERT INTO epistemics.system_expectations (
|
||||
expectation_id, original_intent_id, source_utterance_id, expected_outcome, expected_effect,
|
||||
confidence_millis, evidence_basis, recorded_at_unix_millis)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8) ON CONFLICT DO NOTHING`,
|
||||
value.ID, value.OriginalIntentID, value.SourceUtteranceID, value.ExpectedOutcome, value.ExpectedEffect,
|
||||
value.ConfidenceMillis, value.EvidenceBasis, value.RecordedAtUnixMillis)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *EpistemicsRepository) RecentExperienceRecords(ctx context.Context, limit int) ([]epistemics.ExperienceRecord, error) {
|
||||
if limit < 1 || limit > 100 {
|
||||
return nil, fmt.Errorf("invalid experience limit")
|
||||
}
|
||||
rows, err := r.pool.Query(ctx, `SELECT
|
||||
experience_id,source_kind,original_intent_id,source_utterance_id,context_refs,help_method,help_method_ref,
|
||||
capability_implementation_ref,capability_usage_observation_ref,capability_gap_ref,skill_ref,expectation_id,
|
||||
expected_outcome,actual_outcome,outcome_ref,outcome_detail,human_response_ref,correction_utterance_id,
|
||||
verification_evidence,confidence_millis,recorded_at_unix_millis
|
||||
FROM epistemics.experience_records ORDER BY recorded_at_unix_millis DESC, experience_id DESC LIMIT $1`, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var result []epistemics.ExperienceRecord
|
||||
for rows.Next() {
|
||||
var value epistemics.ExperienceRecord
|
||||
if err := rows.Scan(&value.ID, &value.SourceKind, &value.OriginalIntentID, &value.SourceUtteranceID, &value.ContextRefs, &value.HelpMethod, &value.HelpMethodRef,
|
||||
&value.CapabilityImplementationRef, &value.CapabilityUsageObservationRef, &value.CapabilityGapRef, &value.SkillRef, &value.ExpectationID,
|
||||
&value.ExpectedOutcome, &value.ActualOutcome, &value.OutcomeRef, &value.OutcomeDetail, &value.HumanResponseRef, &value.CorrectionUtteranceID,
|
||||
&value.VerificationEvidence, &value.ConfidenceMillis, &value.RecordedAtUnixMillis); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, value)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
func (r *EpistemicsRepository) RecentReflectionCandidates(ctx context.Context, limit int) ([]epistemics.ReflectionCandidate, error) {
|
||||
if limit < 1 || limit > 100 {
|
||||
return nil, fmt.Errorf("invalid reflection limit")
|
||||
}
|
||||
rows, err := r.pool.Query(ctx, `SELECT
|
||||
reflection_candidate_id,trigger,state,source_experience_id,original_intent_id,source_utterance_id,
|
||||
summary,evidence_refs,requires_cognition,recorded_at_unix_millis,revision
|
||||
FROM epistemics.reflection_candidates ORDER BY recorded_at_unix_millis DESC, reflection_candidate_id DESC LIMIT $1`, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var result []epistemics.ReflectionCandidate
|
||||
for rows.Next() {
|
||||
var value epistemics.ReflectionCandidate
|
||||
if err := rows.Scan(&value.ID, &value.Trigger, &value.State, &value.SourceExperienceID, &value.OriginalIntentID, &value.SourceUtteranceID,
|
||||
&value.Summary, &value.EvidenceRefs, &value.RequiresCognition, &value.RecordedAtUnixMillis, &value.Revision); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, value)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
|
||||
func (r *EpistemicsRepository) SaveExperienceRecord(ctx context.Context, value epistemics.ExperienceRecord) error {
|
||||
_, err := r.pool.Exec(ctx, `INSERT INTO epistemics.experience_records (
|
||||
experience_id, source_kind, original_intent_id, source_utterance_id, context_refs, help_method, help_method_ref,
|
||||
capability_implementation_ref, capability_usage_observation_ref, capability_gap_ref, skill_ref, expectation_id,
|
||||
expected_outcome, actual_outcome, outcome_ref, outcome_detail, human_response_ref, correction_utterance_id,
|
||||
verification_evidence, confidence_millis, recorded_at_unix_millis)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21) ON CONFLICT DO NOTHING`,
|
||||
value.ID, value.SourceKind, value.OriginalIntentID, value.SourceUtteranceID, value.ContextRefs, value.HelpMethod, value.HelpMethodRef,
|
||||
value.CapabilityImplementationRef, value.CapabilityUsageObservationRef, value.CapabilityGapRef, value.SkillRef, value.ExpectationID,
|
||||
value.ExpectedOutcome, value.ActualOutcome, value.OutcomeRef, value.OutcomeDetail, value.HumanResponseRef, value.CorrectionUtteranceID,
|
||||
value.VerificationEvidence, value.ConfidenceMillis, value.RecordedAtUnixMillis)
|
||||
return err
|
||||
}
|
||||
|
||||
func (r *EpistemicsRepository) SaveReflectionCandidate(ctx context.Context, value epistemics.ReflectionCandidate) error {
|
||||
_, err := r.pool.Exec(ctx, `INSERT INTO epistemics.reflection_candidates (
|
||||
reflection_candidate_id, trigger, state, source_experience_id, original_intent_id, source_utterance_id,
|
||||
summary, evidence_refs, requires_cognition, recorded_at_unix_millis, revision)
|
||||
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11) ON CONFLICT DO NOTHING`,
|
||||
value.ID, value.Trigger, value.State, value.SourceExperienceID, value.OriginalIntentID, value.SourceUtteranceID,
|
||||
value.Summary, value.EvidenceRefs, value.RequiresCognition, value.RecordedAtUnixMillis, value.Revision)
|
||||
return err
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
type IdentityRepository struct{ pool *pgxpool.Pool }
|
||||
|
||||
func OpenIdentity(ctx context.Context, config ContinuityConfig) (*IdentityRepository, error) {
|
||||
pool, err := pgxpool.New(ctx, fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s", config.Host, config.Port, config.Database, config.User, config.Password))
|
||||
pool, err := openPool(ctx, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/interaction"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
@@ -11,7 +10,7 @@ import (
|
||||
type InteractionRepository struct{ pool *pgxpool.Pool }
|
||||
|
||||
func OpenInteraction(ctx context.Context, c ContinuityConfig) (*InteractionRepository, error) {
|
||||
p, e := pgxpool.New(ctx, fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s", c.Host, c.Port, c.Database, c.User, c.Password))
|
||||
p, e := openPool(ctx, c)
|
||||
return &InteractionRepository{p}, e
|
||||
}
|
||||
func (r *InteractionRepository) Close() { r.pool.Close() }
|
||||
|
||||
@@ -2,7 +2,6 @@ package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/identity"
|
||||
@@ -13,8 +12,7 @@ import (
|
||||
type SituationRepository struct{ pool *pgxpool.Pool }
|
||||
|
||||
func OpenSituation(ctx context.Context, config ContinuityConfig) (*SituationRepository, error) {
|
||||
dsn := fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s", config.Host, config.Port, config.Database, config.User, config.Password)
|
||||
pool, err := pgxpool.New(ctx, dsn)
|
||||
pool, err := openPool(ctx, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@ import (
|
||||
type WorkRepository struct{ pool *pgxpool.Pool }
|
||||
|
||||
func OpenWork(ctx context.Context, config ContinuityConfig) (*WorkRepository, error) {
|
||||
dsn := fmt.Sprintf("host=%s port=%s dbname=%s user=%s password=%s", config.Host, config.Port, config.Database, config.User, config.Password)
|
||||
pool, err := pgxpool.New(ctx, dsn)
|
||||
pool, err := openPool(ctx, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# ADR-0016 — Experience, Reflection and Skill Evolution Foundation V1
|
||||
|
||||
Status: Accepted
|
||||
|
||||
Task: DEV-000067
|
||||
|
||||
## Context
|
||||
|
||||
Domovoy can now receive human utterances, interpret intent, optionally plan work, resolve capabilities, perform narrow actions through existing owner boundaries, observe outcomes and accept human responses. The next architecture step is to preserve what happened and why, so repeated successful understanding may become reusable skill while failures, corrections and drift can return the system to broader thinking.
|
||||
|
||||
This must not introduce a giant learning manager, self-modifying loop, prompt optimizer or permanent hand-coded user profile. Learning evidence must remain historical and revisable.
|
||||
|
||||
## Decision
|
||||
|
||||
1. Do not create a new canonical owner for DEV-000067.
|
||||
2. `epistemics` owns provider-neutral experience, expectation, outcome and reflection-candidate semantics because these are evidence/provenance about what happened and why. It stores typed references to Interaction, Continuity, Work and Capability records rather than copying their canonical state.
|
||||
3. `capability` owns skill-profile and fast-path lifecycle semantics because Skill V1 is a reusable capability/implementation pattern derived from evidence, not a new authority or profession category.
|
||||
4. A `SystemExpectation` records what the system expected from meaningful help before outcome is known. "Model returned text" and "HTTP 200" are not sufficient success semantics.
|
||||
5. `ExperienceRecord` links original human intent/utterance, context refs, selected help method, capability/implementation refs, expectation, outcome class, human response/correction refs and verification evidence.
|
||||
6. `ReflectionCandidate` is append-only evidence that broader reflection may be warranted. It may be deterministic or routed through the Cognitive Gateway, but model output cannot directly mutate canonical beliefs, skills, authority, Go source or deployment.
|
||||
7. Skill lifecycle is explicit: `CANDIDATE`, `EVALUATING`, `VALIDATED`, `DEGRADED`, `SUSPENDED`, `RETIRED`. Replacing or suspending a skill never deletes source observations, experiences, corrections, outcomes, beliefs or original human intent history.
|
||||
8. Fast paths are optimizations, not truth. Every optimized path must be able to return `NOT_APPLICABLE`, `LOW_CONFIDENCE`, `OUT_OF_DISTRIBUTION`, `CONFLICT` or `STALE` and escalate to broader reasoning/clarification.
|
||||
9. User corrections are strong learning evidence but are not automatically permanent rules. They create experience/reflection evidence and may later support revisable beliefs or skill changes.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Interaction remains canonical owner of conversations, utterances, original human intent and corrections.
|
||||
- Continuity remains canonical owner of HumanResponse, Attention and ProspectiveItem continuation state.
|
||||
- Capability remains canonical owner of capability implementations, gaps, usage observations and skill profiles.
|
||||
- Epistemics records evidence/reflection using typed refs only; it does not mutate other owners.
|
||||
- No external mutation, new authority, new provider adapter, deployment authority expansion or self-modifying code path is introduced.
|
||||
- Cognitive reflection, if later used, must go through Cognitive Gateway/Router and structured validation.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- No LearningManager, reinforcement-learning platform, autonomous curriculum, skill dashboard, universal user profile, plugin marketplace or self-modifying development loop.
|
||||
- No automatic activation/promotion of cognitive models or capability implementations.
|
||||
- No new canonical owner.
|
||||
|
||||
## Consequences
|
||||
|
||||
- Production evidence such as reminder acceptance, human correction, capability gaps, cognitive advisory outcomes and repeated capability failures can be represented consistently.
|
||||
- Validated fast paths can stay cheap, while stale/low-confidence/out-of-distribution cases can return to thinking instead of being forced into old rules.
|
||||
- Knowledge and experience survive skill implementation replacement.
|
||||
@@ -84,7 +84,10 @@
|
||||
"Observation",
|
||||
"Claim",
|
||||
"Belief",
|
||||
"Provenance"
|
||||
"Provenance",
|
||||
"SystemExpectation",
|
||||
"ExperienceRecord",
|
||||
"ReflectionCandidate"
|
||||
],
|
||||
"database_schema": "epistemics",
|
||||
"boundary_class": "remote_capable",
|
||||
@@ -122,7 +125,10 @@
|
||||
"CapabilitySelection",
|
||||
"CapabilityGap",
|
||||
"AcquisitionStrategyCandidate",
|
||||
"CapabilityUsageObservation"
|
||||
"CapabilityUsageObservation",
|
||||
"SkillProfile",
|
||||
"SkillEvidence",
|
||||
"FastPathDecision"
|
||||
],
|
||||
"database_schema": "capability",
|
||||
"boundary_class": "remote_capable",
|
||||
|
||||
@@ -34,7 +34,7 @@ modules:
|
||||
|
||||
epistemics:
|
||||
id: mod.epistemics
|
||||
owns: [Observation, Claim, Belief, Provenance]
|
||||
owns: [Observation, Claim, Belief, Provenance, SystemExpectation, ExperienceRecord, ReflectionCandidate]
|
||||
database_schema: epistemics
|
||||
boundary_class: remote_capable
|
||||
may_depend_on: [contracts.identity, contracts.artifact, platform.events]
|
||||
@@ -48,7 +48,7 @@ modules:
|
||||
|
||||
capability:
|
||||
id: mod.capability
|
||||
owns: [Capability, CapabilityBinding, CapabilityHealth, CapabilityImplementation, CapabilityResolution, CapabilitySelection, CapabilityGap, AcquisitionStrategyCandidate, CapabilityUsageObservation]
|
||||
owns: [Capability, CapabilityBinding, CapabilityHealth, CapabilityImplementation, CapabilityResolution, CapabilitySelection, CapabilityGap, AcquisitionStrategyCandidate, CapabilityUsageObservation, SkillProfile, SkillEvidence, FastPathDecision]
|
||||
database_schema: capability
|
||||
boundary_class: remote_capable
|
||||
may_depend_on: [contracts.identity, contracts.authority, contracts.artifact]
|
||||
|
||||
@@ -144,10 +144,10 @@ func resolveChatCapability(ctx context.Context, repository *postgres.CapabilityR
|
||||
return resolution, err
|
||||
}
|
||||
|
||||
func observeCapabilityUsage(ctx context.Context, repository *postgres.CapabilityRepository, selected capability.ResolvedCapabilityImplementation, requirement capability.CapabilityResolutionRequirement, outcome capability.UsageOutcomeClass, outcomeRef string, started time.Time, now time.Time) {
|
||||
func observeCapabilityUsage(ctx context.Context, repository *postgres.CapabilityRepository, selected capability.ResolvedCapabilityImplementation, requirement capability.CapabilityResolutionRequirement, outcome capability.UsageOutcomeClass, outcomeRef string, started time.Time, now time.Time) (capability.CapabilityUsageObservationID, error) {
|
||||
id, err := ids.New()
|
||||
if err != nil {
|
||||
return
|
||||
return "", err
|
||||
}
|
||||
latency := now.Sub(started).Milliseconds()
|
||||
observation := capability.CapabilityUsageObservation{
|
||||
@@ -164,7 +164,10 @@ func observeCapabilityUsage(ctx context.Context, repository *postgres.Capability
|
||||
LatencyMillis: &latency,
|
||||
ObservedAtUnixMillis: now.UnixMilli(),
|
||||
}
|
||||
_ = repository.SaveCapabilityUsageObservation(ctx, observation)
|
||||
if err := repository.SaveCapabilityUsageObservation(ctx, observation); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return observation.ID, nil
|
||||
}
|
||||
|
||||
func buildReminderRequest(message string, session identity.CreatorSession, intent interaction.OriginalHumanIntent, utterance interaction.HumanUtterance, now time.Time) (continuity.ScheduleReminderRequest, error) {
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
"git.cin.su/m/Assistant/contracts/continuity"
|
||||
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||
"git.cin.su/m/Assistant/contracts/interaction"
|
||||
capabilitydomain "git.cin.su/m/Assistant/modules/capability/domain"
|
||||
epistemicsdomain "git.cin.su/m/Assistant/modules/epistemics/domain"
|
||||
)
|
||||
|
||||
const reminderResponseSkillID = capability.SkillProfileID("skill-obvious-reminder-response-v1")
|
||||
|
||||
type learningEpistemicsStore interface {
|
||||
SaveSystemExpectation(context.Context, epistemics.SystemExpectation) error
|
||||
SaveExperienceRecord(context.Context, epistemics.ExperienceRecord) error
|
||||
SaveReflectionCandidate(context.Context, epistemics.ReflectionCandidate) error
|
||||
}
|
||||
|
||||
type learningCapabilityStore interface {
|
||||
LoadSkillProfile(context.Context, capability.SkillProfileID) (capability.SkillProfile, error)
|
||||
SaveSkillProfile(context.Context, capability.SkillProfile) error
|
||||
}
|
||||
|
||||
type learningRecorder struct {
|
||||
epistemics learningEpistemicsStore
|
||||
capability capabilityLearningStore
|
||||
}
|
||||
|
||||
type capabilityLearningStore interface {
|
||||
learningCapabilityStore
|
||||
}
|
||||
|
||||
type reminderResponseLearningInput struct {
|
||||
Utterance interaction.HumanUtterance
|
||||
Intent interaction.OriginalHumanIntent
|
||||
Result continuity.HumanResponseResult
|
||||
At time.Time
|
||||
}
|
||||
|
||||
type correctionLearningInput struct {
|
||||
Utterance interaction.HumanUtterance
|
||||
Intent interaction.OriginalHumanIntent
|
||||
At time.Time
|
||||
}
|
||||
|
||||
type fastPathDeclineLearningInput struct {
|
||||
Utterance interaction.HumanUtterance
|
||||
Intent interaction.OriginalHumanIntent
|
||||
Result continuity.HumanResponseResult
|
||||
Reason capability.FastPathDeclineReason
|
||||
At time.Time
|
||||
}
|
||||
|
||||
type capabilityLearningInput struct {
|
||||
Utterance interaction.HumanUtterance
|
||||
Intent interaction.OriginalHumanIntent
|
||||
ImplementationID capability.CapabilityImplementationID
|
||||
UsageObservation capability.CapabilityUsageObservationID
|
||||
GapID capability.CapabilityGapID
|
||||
Outcome capability.UsageOutcomeClass
|
||||
OutcomeRef string
|
||||
At time.Time
|
||||
}
|
||||
|
||||
func (r learningRecorder) recordReminderResponse(ctx context.Context, input reminderResponseLearningInput) {
|
||||
if r.epistemics == nil || r.capability == nil || input.Intent.ID == "" || input.Utterance.ID == "" || input.Result.Response.ID == "" {
|
||||
return
|
||||
}
|
||||
expectedEffect := "resolve obvious reminder continuation using deterministic target resolver"
|
||||
if input.Result.Response.TargetProspectiveID != "" {
|
||||
expectedEffect += " for prospective item " + string(input.Result.Response.TargetProspectiveID)
|
||||
}
|
||||
expectation := epistemics.SystemExpectation{
|
||||
OriginalIntentID: epistemics.OriginalIntentRef(input.Intent.ID),
|
||||
SourceUtteranceID: epistemics.SourceUtteranceRef(input.Utterance.ID),
|
||||
ExpectedOutcome: epistemics.OutcomeSuccess,
|
||||
ExpectedEffect: expectedEffect,
|
||||
ConfidenceMillis: 850,
|
||||
EvidenceBasis: []epistemics.EvidenceRef{"continuity:response-target-resolution"},
|
||||
RecordedAtUnixMillis: input.At.UnixMilli(),
|
||||
}
|
||||
expectation, err := epistemicsdomain.NewSystemExpectation(expectation)
|
||||
if err != nil {
|
||||
log.Printf("learning reminder expectation invalid: %v", err)
|
||||
return
|
||||
}
|
||||
if err := r.epistemics.SaveSystemExpectation(ctx, expectation); err != nil {
|
||||
log.Printf("learning reminder expectation unavailable: %v", err)
|
||||
return
|
||||
}
|
||||
actual, confidence := outcomeForHumanResponse(input.Result.Response)
|
||||
experience := epistemics.ExperienceRecord{
|
||||
SourceKind: epistemics.ExperienceSourceContinuity,
|
||||
OriginalIntentID: epistemics.OriginalIntentRef(input.Intent.ID),
|
||||
SourceUtteranceID: epistemics.SourceUtteranceRef(input.Utterance.ID),
|
||||
ContextRefs: contextRefs(
|
||||
"continuity:human-response:"+string(input.Result.Response.ID),
|
||||
"continuity:prospective:"+string(input.Result.Response.TargetProspectiveID),
|
||||
"continuity:attention:"+string(input.Result.Response.TargetAttentionID),
|
||||
),
|
||||
HelpMethod: epistemics.HelpMethodDeterministicFastPath,
|
||||
HelpMethodRef: "continuity:human-response-target-resolver-v1",
|
||||
SkillRef: epistemics.SkillRef(reminderResponseSkillID),
|
||||
ExpectationID: expectation.ID,
|
||||
ExpectedOutcome: expectation.ExpectedOutcome,
|
||||
ActualOutcome: actual,
|
||||
OutcomeRef: "continuity:human-response:" + string(input.Result.Response.ID),
|
||||
OutcomeDetail: string(input.Result.Response.Operation) + " -> " + string(input.Result.Response.Outcome),
|
||||
HumanResponseRef: epistemics.HumanResponseRef(input.Result.Response.ID),
|
||||
VerificationEvidence: []epistemics.EvidenceRef{epistemics.EvidenceRef("human-response:" + string(input.Result.Response.Outcome))},
|
||||
ConfidenceMillis: confidence,
|
||||
RecordedAtUnixMillis: input.At.UnixMilli(),
|
||||
}
|
||||
r.saveExperienceAndReflection(ctx, experience)
|
||||
r.updateReminderSkill(ctx, experience, skillOutcomeForExperience(experience), false)
|
||||
}
|
||||
|
||||
func (r learningRecorder) recordCorrection(ctx context.Context, input correctionLearningInput) {
|
||||
if r.epistemics == nil || input.Intent.ID == "" || input.Utterance.ID == "" {
|
||||
return
|
||||
}
|
||||
experience := epistemics.ExperienceRecord{
|
||||
SourceKind: epistemics.ExperienceSourceInteraction,
|
||||
OriginalIntentID: epistemics.OriginalIntentRef(input.Intent.ID),
|
||||
SourceUtteranceID: epistemics.SourceUtteranceRef(input.Utterance.ID),
|
||||
HelpMethod: epistemics.HelpMethodClarification,
|
||||
SkillRef: epistemics.SkillRef(reminderResponseSkillID),
|
||||
ExpectedOutcome: epistemics.OutcomeSuccess,
|
||||
ActualOutcome: epistemics.OutcomeUserCorrected,
|
||||
OutcomeDetail: "human correction indicates prior interpretation or target resolution may be wrong",
|
||||
CorrectionUtteranceID: epistemics.SourceUtteranceRef(input.Utterance.ID),
|
||||
VerificationEvidence: []epistemics.EvidenceRef{"interaction:human-correction"},
|
||||
ConfidenceMillis: 1000,
|
||||
RecordedAtUnixMillis: input.At.UnixMilli(),
|
||||
}
|
||||
saved, ok := r.saveExperienceAndReflection(ctx, experience)
|
||||
if ok {
|
||||
r.updateReminderSkill(ctx, saved, capability.UsageFailed, true)
|
||||
}
|
||||
}
|
||||
|
||||
func (r learningRecorder) recordFastPathDecline(ctx context.Context, input fastPathDeclineLearningInput) {
|
||||
if r.epistemics == nil || input.Intent.ID == "" || input.Utterance.ID == "" {
|
||||
return
|
||||
}
|
||||
experience, err := epistemicsdomain.FastPathDeclineExperience(
|
||||
epistemics.OriginalIntentRef(input.Intent.ID),
|
||||
epistemics.SourceUtteranceRef(input.Utterance.ID),
|
||||
epistemics.SkillRef(reminderResponseSkillID),
|
||||
string(outcomeForDecline(input.Reason)),
|
||||
input.At.UnixMilli(),
|
||||
)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
if input.Result.Response.ID != "" {
|
||||
experience.ContextRefs = contextRefs("continuity:response-candidate:" + string(input.Result.Response.Operation))
|
||||
}
|
||||
saved, ok := r.saveExperienceAndReflection(ctx, experience)
|
||||
if ok {
|
||||
r.updateReminderSkill(ctx, saved, capability.UsageSkipped, false)
|
||||
}
|
||||
}
|
||||
|
||||
func (r learningRecorder) recordCapabilityUse(ctx context.Context, input capabilityLearningInput) {
|
||||
if r.epistemics == nil || input.Intent.ID == "" || input.Utterance.ID == "" {
|
||||
return
|
||||
}
|
||||
actual := epistemics.OutcomeSuccess
|
||||
switch input.Outcome {
|
||||
case capability.UsageFailed:
|
||||
actual = epistemics.OutcomeFailed
|
||||
case capability.UsageSkipped:
|
||||
actual = epistemics.OutcomeNotApplicable
|
||||
case capability.UsageUnknown:
|
||||
actual = epistemics.OutcomeUnknown
|
||||
}
|
||||
experience := epistemics.ExperienceRecord{
|
||||
SourceKind: epistemics.ExperienceSourceCapability,
|
||||
OriginalIntentID: epistemics.OriginalIntentRef(input.Intent.ID),
|
||||
SourceUtteranceID: epistemics.SourceUtteranceRef(input.Utterance.ID),
|
||||
HelpMethod: epistemics.HelpMethodCapabilityExecution,
|
||||
CapabilityImplementationRef: epistemics.CapabilityImplementationRef(input.ImplementationID),
|
||||
CapabilityUsageObservationRef: epistemics.CapabilityUsageObservationRef(input.UsageObservation),
|
||||
CapabilityGapRef: epistemics.CapabilityGapRef(input.GapID),
|
||||
ExpectedOutcome: epistemics.OutcomeSuccess,
|
||||
ActualOutcome: actual,
|
||||
OutcomeRef: input.OutcomeRef,
|
||||
OutcomeDetail: "capability outcome " + string(input.Outcome),
|
||||
VerificationEvidence: []epistemics.EvidenceRef{"capability:usage-observation"},
|
||||
ConfidenceMillis: 900,
|
||||
RecordedAtUnixMillis: input.At.UnixMilli(),
|
||||
}
|
||||
r.saveExperienceAndReflection(ctx, experience)
|
||||
}
|
||||
|
||||
func (r learningRecorder) saveExperienceAndReflection(ctx context.Context, value epistemics.ExperienceRecord) (epistemics.ExperienceRecord, bool) {
|
||||
value, err := epistemicsdomain.NewExperienceRecord(value)
|
||||
if err != nil {
|
||||
log.Printf("learning experience invalid: %v", err)
|
||||
return epistemics.ExperienceRecord{}, false
|
||||
}
|
||||
if err := r.epistemics.SaveExperienceRecord(ctx, value); err != nil {
|
||||
log.Printf("learning experience unavailable: %v", err)
|
||||
return epistemics.ExperienceRecord{}, false
|
||||
}
|
||||
if candidate, ok, err := epistemicsdomain.ReflectionForExperience(value); err == nil && ok {
|
||||
if err := r.epistemics.SaveReflectionCandidate(ctx, candidate); err != nil {
|
||||
log.Printf("learning reflection unavailable: %v", err)
|
||||
}
|
||||
} else if err != nil {
|
||||
log.Printf("learning reflection invalid: %v", err)
|
||||
}
|
||||
return value, true
|
||||
}
|
||||
|
||||
func (r learningRecorder) updateReminderSkill(ctx context.Context, experience epistemics.ExperienceRecord, outcome capability.UsageOutcomeClass, corrected bool) {
|
||||
if r.capability == nil || experience.ID == "" {
|
||||
return
|
||||
}
|
||||
profile, err := r.capability.LoadSkillProfile(ctx, reminderResponseSkillID)
|
||||
if err != nil {
|
||||
log.Printf("learning skill profile unavailable: %v", err)
|
||||
return
|
||||
}
|
||||
next, err := capabilitydomain.ObserveSkillOutcome(profile, capability.SkillOutcomeObservation{
|
||||
ExperienceRef: capability.SkillEvidenceRef("epistemics:experience:" + string(experience.ID)),
|
||||
Outcome: outcome,
|
||||
UserCorrected: corrected,
|
||||
ObservedAtUnixMillis: experience.RecordedAtUnixMillis,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("learning skill observation invalid: %v", err)
|
||||
return
|
||||
}
|
||||
if err := r.capability.SaveSkillProfile(ctx, next); err != nil {
|
||||
log.Printf("learning skill profile save unavailable: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func outcomeForHumanResponse(response continuity.HumanResponse) (epistemics.OutcomeClass, int64) {
|
||||
if response.ResolutionState != continuity.ResponseResolved {
|
||||
switch response.ResolutionState {
|
||||
case continuity.ResponseAmbiguous:
|
||||
return epistemics.OutcomeConflict, 850
|
||||
case continuity.ResponseNoTarget:
|
||||
return epistemics.OutcomeOutOfDistribution, 850
|
||||
default:
|
||||
return epistemics.OutcomeUnexpected, 700
|
||||
}
|
||||
}
|
||||
switch response.Outcome {
|
||||
case continuity.ResponseOutcomeUserReportedComplete, continuity.ResponseOutcomeSnoozed, continuity.ResponseOutcomeCancelled, continuity.ResponseOutcomeDismissed:
|
||||
return epistemics.OutcomeSuccess, 950
|
||||
case continuity.ResponseOutcomeClarificationNeeded:
|
||||
return epistemics.OutcomeConflict, 850
|
||||
default:
|
||||
return epistemics.OutcomeUnknown, 500
|
||||
}
|
||||
}
|
||||
|
||||
func skillOutcomeForExperience(experience epistemics.ExperienceRecord) capability.UsageOutcomeClass {
|
||||
switch experience.ActualOutcome {
|
||||
case epistemics.OutcomeSuccess, epistemics.OutcomePartialSuccess:
|
||||
return capability.UsageSucceeded
|
||||
case epistemics.OutcomeFailed, epistemics.OutcomeUnexpected, epistemics.OutcomeUserCorrected:
|
||||
return capability.UsageFailed
|
||||
case epistemics.OutcomeUnknown:
|
||||
return capability.UsageUnknown
|
||||
default:
|
||||
return capability.UsageSkipped
|
||||
}
|
||||
}
|
||||
|
||||
func outcomeForDecline(reason capability.FastPathDeclineReason) epistemics.OutcomeClass {
|
||||
switch reason {
|
||||
case capability.FastPathLowConfidence:
|
||||
return epistemics.OutcomeLowConfidence
|
||||
case capability.FastPathOutOfDistribution:
|
||||
return epistemics.OutcomeOutOfDistribution
|
||||
case capability.FastPathConflict:
|
||||
return epistemics.OutcomeConflict
|
||||
case capability.FastPathStale:
|
||||
return epistemics.OutcomeStale
|
||||
default:
|
||||
return epistemics.OutcomeNotApplicable
|
||||
}
|
||||
}
|
||||
|
||||
func contextRefs(values ...string) []epistemics.ContextRef {
|
||||
refs := make([]epistemics.ContextRef, 0, len(values))
|
||||
for _, value := range values {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" || strings.HasSuffix(value, ":") {
|
||||
continue
|
||||
}
|
||||
refs = append(refs, epistemics.ContextRef(value))
|
||||
}
|
||||
return refs
|
||||
}
|
||||
|
||||
func looksLikeUnsupportedContinuation(message string, result continuity.HumanResponseResult) bool {
|
||||
lower := strings.ToLower(strings.TrimSpace(message))
|
||||
if result.Handled {
|
||||
return false
|
||||
}
|
||||
if result.Response.Operation == continuity.ResponseContinue || result.Response.Operation == continuity.ResponseReject {
|
||||
return true
|
||||
}
|
||||
return strings.Contains(lower, "это") && (strings.Contains(lower, "напомин") || strings.Contains(lower, "перенеси") || strings.Contains(lower, "закрой") || strings.Contains(lower, "передвинь"))
|
||||
}
|
||||
|
||||
func declineReasonForResponse(result continuity.HumanResponseResult) capability.FastPathDeclineReason {
|
||||
switch result.Response.Operation {
|
||||
case continuity.ResponseReject:
|
||||
return capability.FastPathConflict
|
||||
case continuity.ResponseContinue:
|
||||
return capability.FastPathNotApplicable
|
||||
default:
|
||||
return capability.FastPathOutOfDistribution
|
||||
}
|
||||
}
|
||||
@@ -68,6 +68,19 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer capabilityRepository.Close()
|
||||
epistemicsPassword, err := readCredentialRefSecret("EPISTEMICS_POSTGRES_PASSWORD", "EPISTEMICS_POSTGRES_CREDENTIAL_REF")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
epistemicsUser := os.Getenv("EPISTEMICS_POSTGRES_USER")
|
||||
if epistemicsUser == "" {
|
||||
epistemicsUser = "epistemics_role"
|
||||
}
|
||||
epistemicsRepository, err := postgres.OpenEpistemics(context.Background(), postgres.ContinuityConfig{Host: os.Getenv("POSTGRES_HOST"), Port: os.Getenv("POSTGRES_PORT"), Database: os.Getenv("POSTGRES_DATABASE"), User: epistemicsUser, Password: epistemicsPassword})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
defer epistemicsRepository.Close()
|
||||
identityRepository, err := postgres.OpenIdentity(context.Background(), postgres.ContinuityConfig{Host: os.Getenv("POSTGRES_HOST"), Port: os.Getenv("POSTGRES_PORT"), Database: os.Getenv("POSTGRES_DATABASE"), User: os.Getenv("IDENTITY_POSTGRES_USER"), Password: os.Getenv("IDENTITY_POSTGRES_PASSWORD")})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
@@ -94,6 +107,7 @@ func main() {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
learning := learningRecorder{epistemics: epistemicsRepository, capability: capabilityRepository}
|
||||
mux := http.NewServeMux()
|
||||
authenticateCreator := func(w http.ResponseWriter, r *http.Request) (identity.CreatorSession, bool) {
|
||||
session, err := identitydomain.ValidateCreatorSession(sessionKey, bearerToken(r), time.Now())
|
||||
@@ -172,6 +186,45 @@ func main() {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(status)
|
||||
})
|
||||
mux.HandleFunc("GET /v1/creator/learning/recent", func(w http.ResponseWriter, r *http.Request) {
|
||||
if _, ok := authenticateCreator(w, r); !ok {
|
||||
return
|
||||
}
|
||||
limit := 10
|
||||
if raw := r.URL.Query().Get("limit"); raw != "" {
|
||||
parsed, err := strconv.Atoi(raw)
|
||||
if err != nil || parsed < 1 || parsed > 50 {
|
||||
http.Error(w, "invalid learning limit", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
limit = parsed
|
||||
}
|
||||
experiences, err := epistemicsRepository.RecentExperienceRecords(r.Context(), limit)
|
||||
if err != nil {
|
||||
log.Printf("learning recent experiences unavailable: %v", err)
|
||||
http.Error(w, "learning experience unavailable", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
reflections, err := epistemicsRepository.RecentReflectionCandidates(r.Context(), limit)
|
||||
if err != nil {
|
||||
log.Printf("learning recent reflections unavailable: %v", err)
|
||||
http.Error(w, "learning reflection unavailable", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
skills, err := capabilityRepository.RecentSkillProfiles(r.Context(), limit)
|
||||
if err != nil {
|
||||
log.Printf("learning recent skills unavailable: %v", err)
|
||||
http.Error(w, "learning skills unavailable", http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("Cache-Control", "no-store")
|
||||
_ = json.NewEncoder(w).Encode(struct {
|
||||
Experiences []epistemics.ExperienceRecord
|
||||
Reflections []epistemics.ReflectionCandidate
|
||||
Skills []capability.SkillProfile
|
||||
}{Experiences: experiences, Reflections: reflections, Skills: skills})
|
||||
})
|
||||
mux.HandleFunc("POST /v1/creator/chat/messages", func(w http.ResponseWriter, r *http.Request) {
|
||||
session, ok := authenticateCreator(w, r)
|
||||
if !ok {
|
||||
@@ -245,6 +298,9 @@ func main() {
|
||||
Message: utterance.Content,
|
||||
RequestedAtUnixMillis: now.UnixMilli(),
|
||||
})
|
||||
if semantic.Interpretation.SpeechAct == interaction.SpeechActCorrection {
|
||||
learning.recordCorrection(r.Context(), correctionLearningInput{Utterance: utterance, Intent: intent, At: now})
|
||||
}
|
||||
activeFocusName := ""
|
||||
activeFocusProvenance := situation.ProjectResolutionProvenance("")
|
||||
if semantic.ActiveFocus != nil {
|
||||
@@ -270,6 +326,9 @@ func main() {
|
||||
if responseErr == nil && responseResult.Handled {
|
||||
planningReply = responseResult.Reply
|
||||
needsCognition = false
|
||||
learning.recordReminderResponse(r.Context(), reminderResponseLearningInput{Utterance: utterance, Intent: intent, Result: responseResult, At: now})
|
||||
} else if responseErr == nil && looksLikeUnsupportedContinuation(utterance.Content, responseResult) {
|
||||
learning.recordFastPathDecline(r.Context(), fastPathDeclineLearningInput{Utterance: utterance, Intent: intent, Result: responseResult, Reason: declineReasonForResponse(responseResult), At: now})
|
||||
}
|
||||
planningInput := work.PlanningInput{
|
||||
ConversationID: work.ConversationRef(conversation.ID),
|
||||
@@ -320,10 +379,12 @@ func main() {
|
||||
if scheduleErr == nil {
|
||||
reminder = &result.Item
|
||||
planningReply = fmt.Sprintf("Готово. Я создал напоминание: %s.", result.Item.Summary)
|
||||
observeCapabilityUsage(r.Context(), capabilityRepository, resolution.Eligible[0], resolution.Requirement, capability.UsageSucceeded, string(result.Item.ID), started, finished)
|
||||
usageID, _ := observeCapabilityUsage(r.Context(), capabilityRepository, resolution.Eligible[0], resolution.Requirement, capability.UsageSucceeded, string(result.Item.ID), started, finished)
|
||||
learning.recordCapabilityUse(r.Context(), capabilityLearningInput{Utterance: utterance, Intent: intent, ImplementationID: resolution.Eligible[0].Implementation.ID, UsageObservation: usageID, Outcome: capability.UsageSucceeded, OutcomeRef: "continuity:prospective:" + string(result.Item.ID), At: finished})
|
||||
} else {
|
||||
planningReply = "Scheduling capability сейчас недоступна: напоминание не создано. Я не буду притворяться, что действие выполнено."
|
||||
observeCapabilityUsage(r.Context(), capabilityRepository, resolution.Eligible[0], resolution.Requirement, capability.UsageFailed, "continuity-reminder-unavailable", started, finished)
|
||||
usageID, _ := observeCapabilityUsage(r.Context(), capabilityRepository, resolution.Eligible[0], resolution.Requirement, capability.UsageFailed, "continuity-reminder-unavailable", started, finished)
|
||||
learning.recordCapabilityUse(r.Context(), capabilityLearningInput{Utterance: utterance, Intent: intent, ImplementationID: resolution.Eligible[0].Implementation.ID, UsageObservation: usageID, Outcome: capability.UsageFailed, OutcomeRef: "continuity-reminder-unavailable", At: finished})
|
||||
}
|
||||
} else {
|
||||
planningReply = "Мне нужно уточнить время напоминания. Например: «Напомни завтра в 10 позвонить врачу»."
|
||||
@@ -332,6 +393,7 @@ func main() {
|
||||
} else if resolution.Gap != nil {
|
||||
capabilityGapID = resolution.Gap.ID
|
||||
planningReply = "Сейчас scheduling capability недоступна, поэтому напоминание не создано. Я зафиксировал capability gap."
|
||||
learning.recordCapabilityUse(r.Context(), capabilityLearningInput{Utterance: utterance, Intent: intent, GapID: resolution.Gap.ID, Outcome: capability.UsageSkipped, OutcomeRef: "capability:gap:" + string(resolution.Gap.ID), At: now})
|
||||
}
|
||||
needsCognition = false
|
||||
}
|
||||
@@ -344,6 +406,7 @@ func main() {
|
||||
} else if resolution.Gap != nil {
|
||||
capabilityGapID = resolution.Gap.ID
|
||||
planningReply = "Сейчас у меня ещё не подключена возможность отправлять почту. Я зафиксировал capability gap, без ложного выполнения."
|
||||
learning.recordCapabilityUse(r.Context(), capabilityLearningInput{Utterance: utterance, Intent: intent, GapID: resolution.Gap.ID, Outcome: capability.UsageSkipped, OutcomeRef: "capability:gap:" + string(resolution.Gap.ID), At: now})
|
||||
}
|
||||
needsCognition = false
|
||||
}
|
||||
|
||||
@@ -536,6 +536,8 @@ func creatorRoute(method, path string) (routeTarget, string, bool) {
|
||||
if validID(id) {
|
||||
return targetCore, "/v1/creator/capabilities/" + id + "/health", true
|
||||
}
|
||||
case method == http.MethodGet && path == "/api/learning/recent":
|
||||
return targetCore, "/v1/creator/learning/recent", true
|
||||
case method == http.MethodGet && strings.HasPrefix(path, "/api/prospective-items/"):
|
||||
id := strings.TrimPrefix(path, "/api/prospective-items/")
|
||||
if validID(id) {
|
||||
|
||||
@@ -141,9 +141,11 @@ button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px
|
||||
.register-empty { margin: 0; padding: 22px 18px; color: var(--faint); font-size: 11px; }
|
||||
|
||||
.activity-panel { margin-bottom: 18px; border: 1px solid var(--line); background: rgba(13, 20, 17, .9); }
|
||||
.learning-panel { margin-bottom: 18px; border: 1px solid rgba(120,198,163,.3); background: linear-gradient(135deg, rgba(22,31,27,.92), rgba(10,16,14,.94)); box-shadow: inset 3px 0 0 rgba(120,198,163,.38); }
|
||||
.activity-panel > header { min-height: 66px; padding: 13px 18px; display: flex; align-items: center; justify-content: space-between; gap: 18px; border-bottom: 1px solid var(--line); }
|
||||
.activity-panel header p { margin: 0 0 6px; color: var(--faint); font: 9px/1 var(--mono); letter-spacing: .13em; text-transform: uppercase; }
|
||||
.activity-panel h2 { margin: 0; font: 400 22px/1 var(--serif); }
|
||||
.learning-panel > header { min-height: 66px; padding: 13px 18px; display: flex; align-items: center; justify-content: space-between; gap: 18px; border-bottom: 1px solid var(--line); }
|
||||
.activity-panel header p, .learning-panel header p { margin: 0 0 6px; color: var(--faint); font: 9px/1 var(--mono); letter-spacing: .13em; text-transform: uppercase; }
|
||||
.activity-panel h2, .learning-panel h2 { margin: 0; font: 400 22px/1 var(--serif); }
|
||||
.activity-tools { display: flex; align-items: center; gap: 12px; color: var(--muted); font: 9px/1 var(--mono); letter-spacing: .1em; }
|
||||
.activity-tools .icon-button { color: var(--muted); font-size: 18px; }
|
||||
.activity-list { max-height: 300px; overflow: auto; scrollbar-color: var(--faint) transparent; }
|
||||
@@ -167,6 +169,23 @@ button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px
|
||||
.activity-reconcile:disabled { opacity: .55; cursor: wait; }
|
||||
.activity-error { color: var(--bad); }
|
||||
|
||||
.learning-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
||||
.learning-grid article { min-width: 0; border-right: 1px solid var(--line-soft); }
|
||||
.learning-grid article:last-child { border-right: 0; }
|
||||
.learning-grid h3 { margin: 0; padding: 13px 18px 10px; color: var(--signal); font: 600 10px/1 var(--mono); letter-spacing: .12em; text-transform: uppercase; border-bottom: 1px solid var(--line-soft); }
|
||||
.learning-list { max-height: 238px; overflow: auto; scrollbar-color: var(--faint) transparent; }
|
||||
.learning-list[aria-busy="true"] { opacity: .72; }
|
||||
.learning-row { min-height: 66px; padding: 12px 18px; display: grid; gap: 6px; border-bottom: 1px solid var(--line-soft); }
|
||||
.learning-row:last-child { border-bottom: 0; }
|
||||
.learning-row:hover { background: rgba(120,198,163,.05); }
|
||||
.learning-row strong { color: var(--ink); font: 600 10px/1.35 var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.learning-row span { color: var(--muted); font: 10px/1.35 var(--mono); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.learning-row time { color: var(--faint); font: 9px/1.35 var(--mono); }
|
||||
.learning-state { width: fit-content; padding: 5px 8px; border: 1px solid currentColor; border-radius: 100px; color: var(--faint); font: 600 8px/1 var(--mono); letter-spacing: .06em; }
|
||||
.learning-state[data-state="SUCCESS"], .learning-state[data-state="VALIDATED"] { color: var(--good); }
|
||||
.learning-state[data-state="USER_CORRECTED"], .learning-state[data-state="FAILED"], .learning-state[data-state="DEGRADED"], .learning-state[data-state="SUSPENDED"] { color: var(--bad); }
|
||||
.learning-state[data-state="CONFLICT"], .learning-state[data-state="OUT_OF_DISTRIBUTION"], .learning-state[data-state="OPEN"], .learning-state[data-state="EVALUATING"] { color: var(--warn); }
|
||||
|
||||
.data-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px; }
|
||||
.data-card { min-width: 0; padding: 24px; border: 1px solid var(--line); background: linear-gradient(145deg, rgba(22, 31, 27, .94), rgba(13, 20, 17, .98)); box-shadow: 0 10px 40px rgba(0,0,0,.12); animation: reveal .55s both; }
|
||||
.task-card { animation-delay: .08s; }
|
||||
@@ -281,7 +300,11 @@ button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 2px
|
||||
.advisor-result { grid-template-columns: 1fr; gap: 10px; }
|
||||
.register-panel > header { align-items: start; }
|
||||
.activity-panel > header { align-items: flex-start; }
|
||||
.learning-panel > header { align-items: flex-start; }
|
||||
.activity-tools { align-items: flex-end; flex-direction: column-reverse; }
|
||||
.learning-grid { grid-template-columns: 1fr; }
|
||||
.learning-grid article { border-right: 0; border-bottom: 1px solid var(--line-soft); }
|
||||
.learning-grid article:last-child { border-bottom: 0; }
|
||||
.activity-row { padding: 14px 16px; grid-template-columns: minmax(0, 1fr) auto; gap: 8px 12px; }
|
||||
.activity-state { grid-column: 2; grid-row: 1; }
|
||||
.activity-operation { grid-column: 1; grid-row: 1; }
|
||||
|
||||
@@ -84,7 +84,10 @@ async function showConsole() {
|
||||
await loadAdvisoryPolicy();
|
||||
const context = saveContext();
|
||||
if (context["session-id"] && context["workspace-id"]) loadDashboard();
|
||||
else loadActionHistory();
|
||||
else {
|
||||
loadActionHistory();
|
||||
loadLearning();
|
||||
}
|
||||
}
|
||||
|
||||
function endSession(message = "Сессия завершена.") {
|
||||
@@ -147,6 +150,7 @@ contextForm.addEventListener("submit", (event) => {
|
||||
|
||||
byId("refresh-all").addEventListener("click", loadDashboard);
|
||||
byId("refresh-actions").addEventListener("click", loadActionHistory);
|
||||
byId("refresh-learning").addEventListener("click", loadLearning);
|
||||
byId("logout").addEventListener("click", () => endSession());
|
||||
|
||||
advisoryForm.addEventListener("submit", async (event) => {
|
||||
@@ -234,6 +238,7 @@ async function loadDashboard() {
|
||||
}
|
||||
work.push(loadProspectiveRegister(context["prospective-id"]));
|
||||
work.push(loadActionHistory());
|
||||
work.push(loadLearning());
|
||||
if (context["capability-id"]) work.push(loadCapability(context["capability-id"]));
|
||||
else setSignal("capability", "не выбрана", "idle");
|
||||
|
||||
@@ -241,6 +246,106 @@ async function loadDashboard() {
|
||||
setText("last-update", `Обновлено ${new Intl.DateTimeFormat("ru-RU", {hour: "2-digit", minute: "2-digit", second: "2-digit"}).format(new Date())}`);
|
||||
}
|
||||
|
||||
async function loadLearning() {
|
||||
const experienceList = byId("experience-list");
|
||||
const reflectionList = byId("reflection-list");
|
||||
const skillList = byId("skill-list");
|
||||
[experienceList, reflectionList, skillList].forEach((list) => {
|
||||
list.setAttribute("aria-busy", "true");
|
||||
list.replaceChildren();
|
||||
const loading = document.createElement("p");
|
||||
loading.className = "register-empty";
|
||||
loading.textContent = "Читаю learning evidence…";
|
||||
list.append(loading);
|
||||
});
|
||||
try {
|
||||
const result = await request("/api/learning/recent?limit=8");
|
||||
const experiences = Array.isArray(result.Experiences) ? result.Experiences : [];
|
||||
const reflections = Array.isArray(result.Reflections) ? result.Reflections : [];
|
||||
const skills = Array.isArray(result.Skills) ? result.Skills : [];
|
||||
setText("learning-count", `${experiences.length + reflections.length + skills.length} СИГНАЛОВ`);
|
||||
renderLearningList(experienceList, experiences, createExperienceRow, "Experience пока не записан.");
|
||||
renderLearningList(reflectionList, reflections, createReflectionRow, "ReflectionCandidate пока нет.");
|
||||
renderLearningList(skillList, skills, createSkillRow, "SkillProfile пока не загружен.");
|
||||
} catch (error) {
|
||||
[experienceList, reflectionList, skillList].forEach((list) => {
|
||||
list.replaceChildren();
|
||||
const failed = document.createElement("p");
|
||||
failed.className = "register-empty activity-error";
|
||||
failed.textContent = error.message;
|
||||
list.append(failed);
|
||||
});
|
||||
setText("learning-count", "ОШИБКА");
|
||||
} finally {
|
||||
[experienceList, reflectionList, skillList].forEach((list) => list.setAttribute("aria-busy", "false"));
|
||||
}
|
||||
}
|
||||
|
||||
function renderLearningList(list, items, rowFactory, emptyText) {
|
||||
list.replaceChildren();
|
||||
if (items.length === 0) {
|
||||
const empty = document.createElement("p");
|
||||
empty.className = "register-empty";
|
||||
empty.textContent = emptyText;
|
||||
list.append(empty);
|
||||
return;
|
||||
}
|
||||
items.forEach((item) => list.append(rowFactory(item)));
|
||||
}
|
||||
|
||||
function createExperienceRow(item) {
|
||||
const row = document.createElement("article");
|
||||
row.className = "learning-row";
|
||||
const state = document.createElement("em");
|
||||
state.className = "learning-state";
|
||||
state.dataset.state = item.ActualOutcome || "UNKNOWN";
|
||||
state.textContent = item.ActualOutcome || "UNKNOWN";
|
||||
const title = document.createElement("strong");
|
||||
title.textContent = item.HelpMethod || "help method";
|
||||
const detail = document.createElement("span");
|
||||
detail.textContent = item.OutcomeDetail || item.OutcomeRef || "evidence recorded";
|
||||
const time = document.createElement("time");
|
||||
time.textContent = item.RecordedAtUnixMillis ? new Date(item.RecordedAtUnixMillis).toLocaleString("ru-RU") : "время неизвестно";
|
||||
row.append(state, title, detail, time);
|
||||
return row;
|
||||
}
|
||||
|
||||
function createReflectionRow(item) {
|
||||
const row = document.createElement("article");
|
||||
row.className = "learning-row";
|
||||
const state = document.createElement("em");
|
||||
state.className = "learning-state";
|
||||
state.dataset.state = item.State || "OPEN";
|
||||
state.textContent = item.Trigger || "REFLECTION";
|
||||
const title = document.createElement("strong");
|
||||
title.textContent = item.State || "OPEN";
|
||||
const detail = document.createElement("span");
|
||||
detail.textContent = item.Summary || "reflection candidate";
|
||||
const time = document.createElement("time");
|
||||
time.textContent = item.RecordedAtUnixMillis ? new Date(item.RecordedAtUnixMillis).toLocaleString("ru-RU") : "время неизвестно";
|
||||
row.append(state, title, detail, time);
|
||||
return row;
|
||||
}
|
||||
|
||||
function createSkillRow(item) {
|
||||
const row = document.createElement("article");
|
||||
row.className = "learning-row";
|
||||
const state = document.createElement("em");
|
||||
state.className = "learning-state";
|
||||
state.dataset.state = item.Status || "UNKNOWN";
|
||||
state.textContent = item.Status || "UNKNOWN";
|
||||
const title = document.createElement("strong");
|
||||
title.textContent = item.Name || "skill profile";
|
||||
const detail = document.createElement("span");
|
||||
const support = Array.isArray(item.SupportingExperienceRefs) ? item.SupportingExperienceRefs.length : 0;
|
||||
const failures = Array.isArray(item.KnownFailureExperienceRefs) ? item.KnownFailureExperienceRefs.length : 0;
|
||||
detail.textContent = `confidence ${item.ConfidenceMillis ?? "—"} · +${support} / -${failures}`;
|
||||
const time = document.createElement("time");
|
||||
time.textContent = item.RecordedAtUnixMillis ? new Date(item.RecordedAtUnixMillis).toLocaleString("ru-RU") : "время неизвестно";
|
||||
row.append(state, title, detail, time);
|
||||
return row;
|
||||
}
|
||||
|
||||
async function loadActionHistory() {
|
||||
const list = byId("action-history");
|
||||
list.setAttribute("aria-busy", "true");
|
||||
|
||||
@@ -143,6 +143,30 @@
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="learning-panel" aria-labelledby="learning-title">
|
||||
<header>
|
||||
<div><p>Learning / evidence</p><h2 id="learning-title">Опыт и reflection</h2></div>
|
||||
<div class="activity-tools">
|
||||
<span id="learning-count">0 СИГНАЛОВ</span>
|
||||
<button id="refresh-learning" class="icon-button" type="button" aria-label="Обновить опыт" title="Обновить опыт">↻</button>
|
||||
</div>
|
||||
</header>
|
||||
<div class="learning-grid">
|
||||
<article>
|
||||
<h3>Experience</h3>
|
||||
<div id="experience-list" class="learning-list" aria-live="polite" aria-busy="false"><p class="register-empty">Опыт ещё не загружен.</p></div>
|
||||
</article>
|
||||
<article>
|
||||
<h3>Reflection</h3>
|
||||
<div id="reflection-list" class="learning-list" aria-live="polite" aria-busy="false"><p class="register-empty">Reflection ещё не загружен.</p></div>
|
||||
</article>
|
||||
<article>
|
||||
<h3>Skills</h3>
|
||||
<div id="skill-list" class="learning-list" aria-live="polite" aria-busy="false"><p class="register-empty">Skill profile ещё не загружен.</p></div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="data-grid">
|
||||
<article class="data-card focus-card">
|
||||
<header><span class="card-number">01</span><div><p>Situation</p><h2>Активный фокус</h2></div><span id="focus-badge" class="status-badge neutral">НЕ ЗАГРУЖЕН</span></header>
|
||||
|
||||
@@ -9,6 +9,8 @@ type CapabilityImplementationID ids.ID
|
||||
type CapabilityResolutionID ids.ID
|
||||
type CapabilityGapID ids.ID
|
||||
type CapabilityUsageObservationID ids.ID
|
||||
type SkillProfileID ids.ID
|
||||
type SkillEvidenceRef string
|
||||
type RequirementRef string
|
||||
type OriginalIntentRef string
|
||||
type SourceUtteranceRef string
|
||||
@@ -21,6 +23,9 @@ type CapabilityResolutionStatus string
|
||||
type GapReason string
|
||||
type AcquisitionStrategyKind string
|
||||
type UsageOutcomeClass string
|
||||
type SkillLifecycleStatus string
|
||||
type FastPathDecisionState string
|
||||
type FastPathDeclineReason string
|
||||
|
||||
const (
|
||||
ImplementationInternalFunction ImplementationKind = "INTERNAL_FUNCTION"
|
||||
@@ -61,6 +66,24 @@ const (
|
||||
UsageFailed UsageOutcomeClass = "FAILED"
|
||||
UsageSkipped UsageOutcomeClass = "SKIPPED"
|
||||
UsageUnknown UsageOutcomeClass = "UNKNOWN"
|
||||
|
||||
SkillCandidate SkillLifecycleStatus = "CANDIDATE"
|
||||
SkillEvaluating SkillLifecycleStatus = "EVALUATING"
|
||||
SkillValidated SkillLifecycleStatus = "VALIDATED"
|
||||
SkillDegraded SkillLifecycleStatus = "DEGRADED"
|
||||
SkillSuspended SkillLifecycleStatus = "SUSPENDED"
|
||||
SkillRetired SkillLifecycleStatus = "RETIRED"
|
||||
|
||||
FastPathApplied FastPathDecisionState = "APPLIED"
|
||||
FastPathDeclined FastPathDecisionState = "DECLINED"
|
||||
FastPathEscalate FastPathDecisionState = "ESCALATE_TO_THINK"
|
||||
|
||||
FastPathNotApplicable FastPathDeclineReason = "NOT_APPLICABLE"
|
||||
FastPathLowConfidence FastPathDeclineReason = "LOW_CONFIDENCE"
|
||||
FastPathOutOfDistribution FastPathDeclineReason = "OUT_OF_DISTRIBUTION"
|
||||
FastPathConflict FastPathDeclineReason = "CONFLICT"
|
||||
FastPathStale FastPathDeclineReason = "STALE"
|
||||
FastPathImplementationDown FastPathDeclineReason = "IMPLEMENTATION_UNAVAILABLE"
|
||||
)
|
||||
|
||||
// CapabilityDescriptor names what Domovoy can obtain or do. CapabilityKind is
|
||||
@@ -160,3 +183,41 @@ type CapabilityUsageObservation struct {
|
||||
LatencyMillis *int64
|
||||
ObservedAtUnixMillis int64
|
||||
}
|
||||
|
||||
// SkillProfile describes a reusable approach/pattern/capability composition.
|
||||
// It is derived from evidence and is never authority by itself.
|
||||
type SkillProfile struct {
|
||||
ID SkillProfileID
|
||||
Revision int64
|
||||
Name string
|
||||
Description string
|
||||
ApplicableContext []string
|
||||
ImplementationRef string
|
||||
ImplementationVersion string
|
||||
Status SkillLifecycleStatus
|
||||
SupportingExperienceRefs []SkillEvidenceRef
|
||||
KnownFailureExperienceRefs []SkillEvidenceRef
|
||||
ConfidenceMillis int64
|
||||
LastValidatedUnixMillis int64
|
||||
ObservedFailureCount int64
|
||||
ObservedCorrectionCount int64
|
||||
RecordedAtUnixMillis int64
|
||||
}
|
||||
|
||||
type SkillOutcomeObservation struct {
|
||||
ExperienceRef SkillEvidenceRef
|
||||
Outcome UsageOutcomeClass
|
||||
UserCorrected bool
|
||||
ObservedAtUnixMillis int64
|
||||
}
|
||||
|
||||
// FastPathDecision is an explicit optimization decision. Decline/escalation is
|
||||
// successful safety behavior, not a failure to answer.
|
||||
type FastPathDecision struct {
|
||||
SkillProfileID SkillProfileID
|
||||
Decision FastPathDecisionState
|
||||
DeclineReason FastPathDeclineReason
|
||||
RequiresCognition bool
|
||||
Explanation string
|
||||
ObservedAtUnixMillis int64
|
||||
}
|
||||
|
||||
@@ -5,8 +5,25 @@ import "git.cin.su/m/Assistant/platform/ids"
|
||||
type ObservationID ids.ID
|
||||
type ClaimID ids.ID
|
||||
type BeliefID ids.ID
|
||||
type SystemExpectationID ids.ID
|
||||
type ExperienceRecordID ids.ID
|
||||
type ReflectionCandidateID ids.ID
|
||||
type VerificationState string
|
||||
type ClaimSourceKind string
|
||||
type EvidenceRef string
|
||||
type ContextRef string
|
||||
type OriginalIntentRef string
|
||||
type SourceUtteranceRef string
|
||||
type HumanResponseRef string
|
||||
type CapabilityImplementationRef string
|
||||
type CapabilityUsageObservationRef string
|
||||
type CapabilityGapRef string
|
||||
type SkillRef string
|
||||
type ExperienceSourceKind string
|
||||
type HelpMethodKind string
|
||||
type OutcomeClass string
|
||||
type ReflectionTriggerKind string
|
||||
type ReflectionCandidateState string
|
||||
|
||||
const (
|
||||
VerificationUnverified VerificationState = "UNVERIFIED"
|
||||
@@ -17,4 +34,101 @@ const (
|
||||
|
||||
ClaimSourceAuthenticatedHuman ClaimSourceKind = "AUTHENTICATED_HUMAN"
|
||||
ClaimSourceTrustedObservation ClaimSourceKind = "TRUSTED_OBSERVATION"
|
||||
|
||||
ExperienceSourceInteraction ExperienceSourceKind = "INTERACTION"
|
||||
ExperienceSourceContinuity ExperienceSourceKind = "CONTINUITY"
|
||||
ExperienceSourceCapability ExperienceSourceKind = "CAPABILITY"
|
||||
ExperienceSourceCognitiveAdvisory ExperienceSourceKind = "COGNITIVE_ADVISORY"
|
||||
|
||||
HelpMethodDeterministicFastPath HelpMethodKind = "DETERMINISTIC_FAST_PATH"
|
||||
HelpMethodCapabilityExecution HelpMethodKind = "CAPABILITY_EXECUTION"
|
||||
HelpMethodCognitiveAdvisory HelpMethodKind = "COGNITIVE_ADVISORY"
|
||||
HelpMethodClarification HelpMethodKind = "CLARIFICATION"
|
||||
HelpMethodFallback HelpMethodKind = "FALLBACK"
|
||||
|
||||
OutcomeSuccess OutcomeClass = "SUCCESS"
|
||||
OutcomePartialSuccess OutcomeClass = "PARTIAL_SUCCESS"
|
||||
OutcomeFailed OutcomeClass = "FAILED"
|
||||
OutcomeUnknown OutcomeClass = "UNKNOWN"
|
||||
OutcomeUserCorrected OutcomeClass = "USER_CORRECTED"
|
||||
OutcomeUnexpected OutcomeClass = "UNEXPECTED_OUTCOME"
|
||||
OutcomeNotApplicable OutcomeClass = "NOT_APPLICABLE"
|
||||
OutcomeLowConfidence OutcomeClass = "LOW_CONFIDENCE"
|
||||
OutcomeOutOfDistribution OutcomeClass = "OUT_OF_DISTRIBUTION"
|
||||
OutcomeConflict OutcomeClass = "CONFLICT"
|
||||
OutcomeStale OutcomeClass = "STALE"
|
||||
|
||||
ReflectionUnexpectedOutcome ReflectionTriggerKind = "UNEXPECTED_OUTCOME"
|
||||
ReflectionFailure ReflectionTriggerKind = "FAILURE"
|
||||
ReflectionUserCorrection ReflectionTriggerKind = "USER_CORRECTION"
|
||||
ReflectionContradiction ReflectionTriggerKind = "CONTRADICTION"
|
||||
ReflectionLowConfidence ReflectionTriggerKind = "LOW_CONFIDENCE"
|
||||
ReflectionRepeatedDegradation ReflectionTriggerKind = "REPEATED_DEGRADATION"
|
||||
ReflectionEnvironmentChange ReflectionTriggerKind = "ENVIRONMENT_CHANGE"
|
||||
ReflectionImplementationChanged ReflectionTriggerKind = "IMPLEMENTATION_CHANGED"
|
||||
ReflectionStaleSkill ReflectionTriggerKind = "STALE_SKILL"
|
||||
ReflectionSampledAudit ReflectionTriggerKind = "SAMPLED_AUDIT"
|
||||
|
||||
ReflectionCandidateOpen ReflectionCandidateState = "OPEN"
|
||||
ReflectionCandidateEvaluating ReflectionCandidateState = "EVALUATING"
|
||||
ReflectionCandidateSuperseded ReflectionCandidateState = "SUPERSEDED"
|
||||
ReflectionCandidateResolved ReflectionCandidateState = "RESOLVED"
|
||||
)
|
||||
|
||||
// SystemExpectation records what Domovoy expected a meaningful help path to
|
||||
// accomplish before the outcome was known. It is evidence context, not a
|
||||
// commitment or verified fact.
|
||||
type SystemExpectation struct {
|
||||
ID SystemExpectationID
|
||||
OriginalIntentID OriginalIntentRef
|
||||
SourceUtteranceID SourceUtteranceRef
|
||||
ExpectedOutcome OutcomeClass
|
||||
ExpectedEffect string
|
||||
ConfidenceMillis int64
|
||||
EvidenceBasis []EvidenceRef
|
||||
RecordedAtUnixMillis int64
|
||||
}
|
||||
|
||||
// ExperienceRecord is append-only evidence about an interaction/help episode.
|
||||
// It stores typed references to other owners and never copies their canonical
|
||||
// state or grants authority.
|
||||
type ExperienceRecord struct {
|
||||
ID ExperienceRecordID
|
||||
SourceKind ExperienceSourceKind
|
||||
OriginalIntentID OriginalIntentRef
|
||||
SourceUtteranceID SourceUtteranceRef
|
||||
ContextRefs []ContextRef
|
||||
HelpMethod HelpMethodKind
|
||||
HelpMethodRef string
|
||||
CapabilityImplementationRef CapabilityImplementationRef
|
||||
CapabilityUsageObservationRef CapabilityUsageObservationRef
|
||||
CapabilityGapRef CapabilityGapRef
|
||||
SkillRef SkillRef
|
||||
ExpectationID SystemExpectationID
|
||||
ExpectedOutcome OutcomeClass
|
||||
ActualOutcome OutcomeClass
|
||||
OutcomeRef string
|
||||
OutcomeDetail string
|
||||
HumanResponseRef HumanResponseRef
|
||||
CorrectionUtteranceID SourceUtteranceRef
|
||||
VerificationEvidence []EvidenceRef
|
||||
ConfidenceMillis int64
|
||||
RecordedAtUnixMillis int64
|
||||
}
|
||||
|
||||
// ReflectionCandidate records that broader reflection may be warranted.
|
||||
// Cognitive reflection, if used, must go through Cognitive Gateway/Router and
|
||||
// produce candidates only; it cannot directly rewrite canonical state.
|
||||
type ReflectionCandidate struct {
|
||||
ID ReflectionCandidateID
|
||||
Trigger ReflectionTriggerKind
|
||||
State ReflectionCandidateState
|
||||
SourceExperienceID ExperienceRecordID
|
||||
OriginalIntentID OriginalIntentRef
|
||||
SourceUtteranceID SourceUtteranceRef
|
||||
Summary string
|
||||
EvidenceRefs []EvidenceRef
|
||||
RequiresCognition bool
|
||||
RecordedAtUnixMillis int64
|
||||
Revision int64
|
||||
}
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
BEGIN;
|
||||
|
||||
SET ROLE epistemics_role;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS epistemics.system_expectations (
|
||||
expectation_id TEXT PRIMARY KEY,
|
||||
original_intent_id TEXT NOT NULL,
|
||||
source_utterance_id TEXT NOT NULL,
|
||||
expected_outcome TEXT NOT NULL,
|
||||
expected_effect TEXT NOT NULL,
|
||||
confidence_millis BIGINT NOT NULL,
|
||||
evidence_basis TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||
recorded_at_unix_millis BIGINT NOT NULL,
|
||||
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CHECK (expectation_id <> ''),
|
||||
CHECK (original_intent_id <> ''),
|
||||
CHECK (source_utterance_id <> ''),
|
||||
CHECK (expected_outcome IN ('SUCCESS','PARTIAL_SUCCESS','FAILED','UNKNOWN','USER_CORRECTED','UNEXPECTED_OUTCOME','NOT_APPLICABLE','LOW_CONFIDENCE','OUT_OF_DISTRIBUTION','CONFLICT','STALE')),
|
||||
CHECK (expected_effect <> ''),
|
||||
CHECK (confidence_millis BETWEEN 0 AND 1000),
|
||||
CHECK (recorded_at_unix_millis > 0)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS epistemics.experience_records (
|
||||
experience_id TEXT PRIMARY KEY,
|
||||
source_kind TEXT NOT NULL,
|
||||
original_intent_id TEXT NOT NULL,
|
||||
source_utterance_id TEXT NOT NULL,
|
||||
context_refs TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||
help_method TEXT NOT NULL,
|
||||
help_method_ref TEXT NOT NULL DEFAULT '',
|
||||
capability_implementation_ref TEXT NOT NULL DEFAULT '',
|
||||
capability_usage_observation_ref TEXT NOT NULL DEFAULT '',
|
||||
capability_gap_ref TEXT NOT NULL DEFAULT '',
|
||||
skill_ref TEXT NOT NULL DEFAULT '',
|
||||
expectation_id TEXT NOT NULL DEFAULT '',
|
||||
expected_outcome TEXT NOT NULL,
|
||||
actual_outcome TEXT NOT NULL,
|
||||
outcome_ref TEXT NOT NULL DEFAULT '',
|
||||
outcome_detail TEXT NOT NULL DEFAULT '',
|
||||
human_response_ref TEXT NOT NULL DEFAULT '',
|
||||
correction_utterance_id TEXT NOT NULL DEFAULT '',
|
||||
verification_evidence TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||
confidence_millis BIGINT NOT NULL,
|
||||
recorded_at_unix_millis BIGINT NOT NULL,
|
||||
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CHECK (experience_id <> ''),
|
||||
CHECK (source_kind IN ('INTERACTION','CONTINUITY','CAPABILITY','COGNITIVE_ADVISORY')),
|
||||
CHECK (original_intent_id <> ''),
|
||||
CHECK (source_utterance_id <> ''),
|
||||
CHECK (help_method IN ('DETERMINISTIC_FAST_PATH','CAPABILITY_EXECUTION','COGNITIVE_ADVISORY','CLARIFICATION','FALLBACK')),
|
||||
CHECK (expected_outcome IN ('SUCCESS','PARTIAL_SUCCESS','FAILED','UNKNOWN','USER_CORRECTED','UNEXPECTED_OUTCOME','NOT_APPLICABLE','LOW_CONFIDENCE','OUT_OF_DISTRIBUTION','CONFLICT','STALE')),
|
||||
CHECK (actual_outcome IN ('SUCCESS','PARTIAL_SUCCESS','FAILED','UNKNOWN','USER_CORRECTED','UNEXPECTED_OUTCOME','NOT_APPLICABLE','LOW_CONFIDENCE','OUT_OF_DISTRIBUTION','CONFLICT','STALE')),
|
||||
CHECK (confidence_millis BETWEEN 0 AND 1000),
|
||||
CHECK (recorded_at_unix_millis > 0)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS epistemics.reflection_candidates (
|
||||
reflection_candidate_id TEXT PRIMARY KEY,
|
||||
trigger TEXT NOT NULL,
|
||||
state TEXT NOT NULL,
|
||||
source_experience_id TEXT NOT NULL,
|
||||
original_intent_id TEXT NOT NULL,
|
||||
source_utterance_id TEXT NOT NULL,
|
||||
summary TEXT NOT NULL,
|
||||
evidence_refs TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||
requires_cognition BOOLEAN NOT NULL,
|
||||
recorded_at_unix_millis BIGINT NOT NULL,
|
||||
revision BIGINT NOT NULL,
|
||||
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CHECK (reflection_candidate_id <> ''),
|
||||
CHECK (trigger IN ('UNEXPECTED_OUTCOME','FAILURE','USER_CORRECTION','CONTRADICTION','LOW_CONFIDENCE','REPEATED_DEGRADATION','ENVIRONMENT_CHANGE','IMPLEMENTATION_CHANGED','STALE_SKILL','SAMPLED_AUDIT')),
|
||||
CHECK (state IN ('OPEN','EVALUATING','SUPERSEDED','RESOLVED')),
|
||||
CHECK (source_experience_id <> ''),
|
||||
CHECK (original_intent_id <> ''),
|
||||
CHECK (source_utterance_id <> ''),
|
||||
CHECK (summary <> ''),
|
||||
CHECK (recorded_at_unix_millis > 0),
|
||||
CHECK (revision > 0)
|
||||
);
|
||||
|
||||
RESET ROLE;
|
||||
|
||||
SET ROLE capability_role;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS capability.skill_profiles (
|
||||
skill_profile_id TEXT NOT NULL,
|
||||
revision BIGINT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT NOT NULL,
|
||||
applicable_context TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||
implementation_ref TEXT NOT NULL DEFAULT '',
|
||||
implementation_version TEXT NOT NULL DEFAULT '',
|
||||
status TEXT NOT NULL,
|
||||
supporting_experience_refs TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||
known_failure_experience_refs TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[],
|
||||
confidence_millis BIGINT NOT NULL,
|
||||
last_validated_unix_millis BIGINT NOT NULL DEFAULT 0,
|
||||
observed_failure_count BIGINT NOT NULL DEFAULT 0,
|
||||
observed_correction_count BIGINT NOT NULL DEFAULT 0,
|
||||
recorded_at_unix_millis BIGINT NOT NULL,
|
||||
recorded_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (skill_profile_id, revision),
|
||||
CHECK (skill_profile_id <> ''),
|
||||
CHECK (revision > 0),
|
||||
CHECK (name <> ''),
|
||||
CHECK (description <> ''),
|
||||
CHECK (status IN ('CANDIDATE','EVALUATING','VALIDATED','DEGRADED','SUSPENDED','RETIRED')),
|
||||
CHECK (confidence_millis BETWEEN 0 AND 1000),
|
||||
CHECK (last_validated_unix_millis >= 0),
|
||||
CHECK (observed_failure_count >= 0),
|
||||
CHECK (observed_correction_count >= 0),
|
||||
CHECK (recorded_at_unix_millis > 0)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS capability.skill_profile_heads (
|
||||
skill_profile_id TEXT PRIMARY KEY,
|
||||
revision BIGINT NOT NULL,
|
||||
status TEXT NOT NULL,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
CHECK (revision > 0),
|
||||
CHECK (status IN ('CANDIDATE','EVALUATING','VALIDATED','DEGRADED','SUSPENDED','RETIRED'))
|
||||
);
|
||||
|
||||
INSERT INTO capability.skill_profiles(
|
||||
skill_profile_id, revision, name, description, applicable_context, implementation_ref, implementation_version,
|
||||
status, supporting_experience_refs, known_failure_experience_refs, confidence_millis, last_validated_unix_millis,
|
||||
observed_failure_count, observed_correction_count, recorded_at_unix_millis)
|
||||
VALUES (
|
||||
'skill-obvious-reminder-response-v1',
|
||||
1,
|
||||
'Resolve obvious reminder continuation',
|
||||
'Use a recent unambiguous reminder/attention target for complete/snooze/cancel without cognition, while declining ambiguous or unsupported cases.',
|
||||
ARRAY['continuity:reminder-response'],
|
||||
'continuity:human-response-target-resolver-v1',
|
||||
'1',
|
||||
'VALIDATED',
|
||||
ARRAY['continuity:human-response-live-acceptance-dev-000066'],
|
||||
ARRAY[]::TEXT[],
|
||||
850,
|
||||
1,
|
||||
0,
|
||||
0,
|
||||
1
|
||||
)
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
INSERT INTO capability.skill_profile_heads(skill_profile_id, revision, status)
|
||||
VALUES ('skill-obvious-reminder-response-v1', 1, 'VALIDATED')
|
||||
ON CONFLICT (skill_profile_id) DO NOTHING;
|
||||
|
||||
RESET ROLE;
|
||||
COMMIT;
|
||||
@@ -12,6 +12,7 @@ services:
|
||||
SITUATION_DB_PASSWORD: ${SITUATION_DB_PASSWORD:-assistant_situation_dev_only}
|
||||
WORK_DB_PASSWORD: ${WORK_DB_PASSWORD:-assistant_work_dev_only}
|
||||
CAPABILITY_DB_PASSWORD: ${CAPABILITY_DB_PASSWORD:-assistant_capability_dev_only}
|
||||
EPISTEMICS_DB_PASSWORD: ${EPISTEMICS_DB_PASSWORD:-assistant_epistemics_dev_only}
|
||||
COMMIT_DB_PASSWORD: ${COMMIT_DB_PASSWORD:-assistant_commit_dev_only}
|
||||
ports: ["15432:5432"]
|
||||
volumes:
|
||||
@@ -50,6 +51,8 @@ services:
|
||||
WORK_POSTGRES_PASSWORD: ${WORK_DB_PASSWORD:-assistant_work_dev_only}
|
||||
CAPABILITY_POSTGRES_USER: capability_role
|
||||
CAPABILITY_POSTGRES_PASSWORD: ${CAPABILITY_DB_PASSWORD:-assistant_capability_dev_only}
|
||||
EPISTEMICS_POSTGRES_USER: epistemics_role
|
||||
EPISTEMICS_POSTGRES_PASSWORD: ${EPISTEMICS_DB_PASSWORD:-assistant_epistemics_dev_only}
|
||||
ports: ["19080:8080"]
|
||||
depends_on:
|
||||
postgres: { condition: service_healthy }
|
||||
|
||||
@@ -17,6 +17,7 @@ services:
|
||||
WORK_DB_PASSWORD: ${WORK_DB_PASSWORD}
|
||||
CAPABILITY_DB_PASSWORD: ${CAPABILITY_DB_PASSWORD}
|
||||
COMMIT_DB_PASSWORD: ${COMMIT_DB_PASSWORD}
|
||||
EPISTEMICS_DB_PASSWORD_FILE: /run/secrets/epistemics_db_password
|
||||
INTERACTION_DB_PASSWORD_FILE: /run/secrets/interaction_db_password
|
||||
volumes:
|
||||
- assistant_pg:/var/lib/postgresql
|
||||
@@ -72,6 +73,9 @@ services:
|
||||
WORK_POSTGRES_PASSWORD: ${WORK_DB_PASSWORD}
|
||||
CAPABILITY_POSTGRES_USER: capability_role
|
||||
CAPABILITY_POSTGRES_PASSWORD: ${CAPABILITY_DB_PASSWORD}
|
||||
EPISTEMICS_POSTGRES_USER: epistemics_role
|
||||
EPISTEMICS_POSTGRES_CREDENTIAL_REF: credential://domovoy/epistemics/db-password/v1
|
||||
EPISTEMICS_POSTGRES_PASSWORD_FILE: /run/secrets/epistemics_db_password
|
||||
INTERACTION_POSTGRES_USER: interaction_role
|
||||
INTERACTION_POSTGRES_CREDENTIAL_REF: credential://domovoy/interaction/db-password/v1
|
||||
INTERACTION_POSTGRES_PASSWORD_FILE: /run/secrets/interaction_db_password
|
||||
|
||||
@@ -18,6 +18,13 @@ SQL
|
||||
psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -v ON_ERROR_STOP=1 -v capability_password="$CAPABILITY_DB_PASSWORD" <<'SQL'
|
||||
ALTER ROLE capability_role PASSWORD :'capability_password';
|
||||
SQL
|
||||
if [ "${EPISTEMICS_DB_PASSWORD_FILE:-}" != "" ]; then
|
||||
EPISTEMICS_DB_PASSWORD="$(tr -d '\r\n' < "$EPISTEMICS_DB_PASSWORD_FILE")"
|
||||
fi
|
||||
: "${EPISTEMICS_DB_PASSWORD:?EPISTEMICS_DB_PASSWORD or EPISTEMICS_DB_PASSWORD_FILE is required}"
|
||||
psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -v ON_ERROR_STOP=1 -v epistemics_password="$EPISTEMICS_DB_PASSWORD" <<'SQL'
|
||||
ALTER ROLE epistemics_role PASSWORD :'epistemics_password';
|
||||
SQL
|
||||
psql --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" -v ON_ERROR_STOP=1 -v commit_password="$COMMIT_DB_PASSWORD" <<'SQL'
|
||||
ALTER ROLE commit_role PASSWORD :'commit_password';
|
||||
SQL
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$Endpoint = 'https://assistent.cin.su/delivery/v1/receipts?limit=100',
|
||||
[string]$Endpoint = 'https://assistant.cin.su/delivery/v1/receipts?limit=100',
|
||||
[string]$KeyRecordPath = "$env:LOCALAPPDATA\Domovoy\notification-receiver-read-key.dpapi.json",
|
||||
[string]$StatePath = "$env:LOCALAPPDATA\Domovoy\notification-receiver-state.json",
|
||||
[ValidateRange(1, 5)]
|
||||
@@ -24,7 +24,7 @@ function Write-AtomicJson {
|
||||
}
|
||||
|
||||
$uri = [Uri]$Endpoint
|
||||
if ($uri.Scheme -ne 'https' -or $uri.Host -ne 'assistent.cin.su' -or $uri.AbsolutePath -ne '/delivery/v1/receipts') {
|
||||
if ($uri.Scheme -ne 'https' -or $uri.Host -ne 'assistant.cin.su' -or $uri.AbsolutePath -ne '/delivery/v1/receipts') {
|
||||
throw 'Notification receiver endpoint is outside the fixed HTTPS binding'
|
||||
}
|
||||
if (-not (Test-Path -LiteralPath $KeyRecordPath -PathType Leaf)) { throw "Notification receiver key not found: $KeyRecordPath" }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"task_file": "dev/tasks/DEV-000066-human-response-continuation-semantics.json",
|
||||
"task_id": "DEV-000066",
|
||||
"task_file": "dev/tasks/DEV-000068-runtime-experience-reflection-loop.json",
|
||||
"task_id": "DEV-000068",
|
||||
"task_revision": 1
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
{
|
||||
"task_id": "DEV-000067",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0016",
|
||||
"base_repo_revision": "d4640d7617ed579a9b0852fb336905f777f80de6",
|
||||
"risk_tier": "E",
|
||||
"intent": "Add provider-neutral Experience, Reflection and Skill Evolution foundation without creating a LearningManager or new canonical owner.",
|
||||
"requirement_ids": [
|
||||
"ADR-0016",
|
||||
"INV-OWN-001",
|
||||
"INV-EPI-001",
|
||||
"Experience preserves original intent, expectation, outcome and correction evidence",
|
||||
"Fast paths can decline and return to broader reasoning",
|
||||
"Skill replacement does not delete evidence"
|
||||
],
|
||||
"allowed_change_scope": {
|
||||
"modules": [
|
||||
"epistemics",
|
||||
"capability",
|
||||
"interaction",
|
||||
"continuity"
|
||||
],
|
||||
"paths": [
|
||||
"architecture/adrs/ADR-0016-experience-reflection-skill-evolution.md",
|
||||
"architecture/architecture-registry.yaml",
|
||||
"architecture/architecture-registry.json",
|
||||
"contracts/epistemics/**",
|
||||
"contracts/capability/**",
|
||||
"modules/epistemics/**",
|
||||
"modules/capability/**",
|
||||
"adapters/postgres/**",
|
||||
"database/migrations/**",
|
||||
"cmd/core-runtime/**",
|
||||
"cmd/creator-web/**",
|
||||
"deploy/windows/Invoke-DomovoyNotificationReceiver.ps1",
|
||||
"tests/owner_acl/**",
|
||||
"tests/cognitive_advisory/**",
|
||||
"docs/PROJECT_STATUS.md",
|
||||
"dev/ACTIVE_TASK.json",
|
||||
"dev/tasks/DEV-000067-experience-reflection-skill-evolution.json"
|
||||
]
|
||||
},
|
||||
"forbidden_change_scope": {
|
||||
"paths": [
|
||||
"architecture/ARCHITECTURE_CONSTITUTION.md",
|
||||
"modules/authority/**",
|
||||
"modules/commit/**",
|
||||
"cmd/commit-runtime/**",
|
||||
"security/**",
|
||||
"ci/**",
|
||||
".gitea/**"
|
||||
]
|
||||
},
|
||||
"allowed_architecture_delta": {
|
||||
"new_cross_module_dependencies": 0,
|
||||
"new_canonical_owners": 0,
|
||||
"new_privileges": 0,
|
||||
"new_third_party_dependencies": 0,
|
||||
"new_provider_adapters": 0
|
||||
},
|
||||
"contracts": {
|
||||
"consume": [
|
||||
"Interaction HumanUtterance / OriginalHumanIntent / correction refs",
|
||||
"Continuity HumanResponse / Attention / ProspectiveItem refs",
|
||||
"Capability CapabilityUsageObservation / CapabilityGap refs",
|
||||
"Cognitive Gateway / Router for future reflection reasoning only"
|
||||
],
|
||||
"may_change": [
|
||||
"contracts/epistemics@v1",
|
||||
"contracts/capability@v1"
|
||||
]
|
||||
},
|
||||
"semantic_owner": "epistemics",
|
||||
"canonical_state": [
|
||||
"SystemExpectation",
|
||||
"ExperienceRecord",
|
||||
"ReflectionCandidate",
|
||||
"SkillProfile",
|
||||
"SkillEvidence",
|
||||
"FastPathDecision"
|
||||
],
|
||||
"state_transitions": [
|
||||
"successful target resolution + accepted human response -> positive experience evidence",
|
||||
"user correction -> USER_CORRECTED outcome and reflection candidate",
|
||||
"fast path outside supported semantics -> NOT_APPLICABLE/OUT_OF_DISTRIBUTION and escalation",
|
||||
"repeated capability/skill failure evidence -> DEGRADED skill profile without deleting history"
|
||||
],
|
||||
"side_effects_and_privileges": [
|
||||
"No external mutation is introduced",
|
||||
"No cognitive model is required for LLM-free tests",
|
||||
"No model output can directly rewrite skills, beliefs, source, deployment or authority",
|
||||
"Docker-test remains prohibited"
|
||||
],
|
||||
"persistence_and_migrations": [
|
||||
"epistemics.system_expectations append-only table",
|
||||
"epistemics.experience_records append-only table",
|
||||
"epistemics.reflection_candidates append-only table",
|
||||
"capability.skill_profiles revisioned table",
|
||||
"capability.skill_profile_heads projection table",
|
||||
"no cross-owner FK",
|
||||
"no cross-owner SQL join dependency"
|
||||
],
|
||||
"failure_retry_restart": [
|
||||
"Experience/reflection rows are append-only historical evidence",
|
||||
"Fast path decline is a valid non-failure decision and preserves return-to-thinking path",
|
||||
"DEGRADED/SUSPENDED/RETIRED skill profiles preserve supporting/failure evidence refs"
|
||||
],
|
||||
"assumptions": [
|
||||
"DEV-000066 is production baseline",
|
||||
"Existing owners are sufficient; no new canonical owner is needed",
|
||||
"V1 records evidence and lifecycle primitives, not autonomous self-improvement"
|
||||
],
|
||||
"acceptance_scenarios": [
|
||||
"Target resolver selected the right recent reminder and user accepted -> positive experience evidence",
|
||||
"User correction of wrong interpretation/target -> USER_CORRECTED outcome and reflection candidate",
|
||||
"Known deterministic path receives unsupported input -> fast path declines and escalates rather than guessing",
|
||||
"Capability implementation fails repeatedly -> related skill confidence/status can degrade without deleting history",
|
||||
"Deleting/replacing/disabling a skill implementation does not delete source observations, experience, corrections, outcomes or original human intent history",
|
||||
"LLM-free operation passes"
|
||||
],
|
||||
"required_tests": [
|
||||
"go test ./...",
|
||||
"go vet ./...",
|
||||
"go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json",
|
||||
"go run ./cmd/devctl check-architecture",
|
||||
"go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json",
|
||||
"tests/cognitive_advisory/cognitive_worker_env_policy_test.ps1",
|
||||
"tests/owner_acl/verify.ps1 -DockerContext docker-prod -PostgresContainer assistant-postgres-1",
|
||||
"production /chat verification if runtime deployment is required"
|
||||
,
|
||||
"notification receiver endpoint uses canonical assistant.cin.su spelling"
|
||||
],
|
||||
"terminal_gap_states": [
|
||||
"BLOCKED_BY_ARCHITECTURE_GAP",
|
||||
"BLOCKED_BY_SPEC_GAP",
|
||||
"BLOCKED_BY_MISSING_CAPABILITY",
|
||||
"BLOCKED_BY_CONFLICT"
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
{
|
||||
"task_id": "DEV-000068",
|
||||
"task_revision": 1,
|
||||
"architecture_revision": "ADR-0016",
|
||||
"base_repo_revision": "67ff1f73cddfd94f9f5745acc87f997aed49630a",
|
||||
"risk_tier": "E",
|
||||
"intent": "Activate the first production runtime Experience and Reflection loop for meaningful /chat, HumanResponse, capability and fast-path outcomes without creating a LearningManager.",
|
||||
"requirement_ids": [
|
||||
"ADR-0016",
|
||||
"INV-OWN-001",
|
||||
"INV-EPI-001",
|
||||
"Runtime Experience capture",
|
||||
"Runtime ReflectionCandidate triggers",
|
||||
"SkillEvidence from real reminder response outcomes",
|
||||
"Fast-path decline returns to reasoning/clarification"
|
||||
],
|
||||
"allowed_change_scope": {
|
||||
"modules": [
|
||||
"epistemics",
|
||||
"capability",
|
||||
"interaction",
|
||||
"continuity"
|
||||
],
|
||||
"paths": [
|
||||
"contracts/epistemics/**",
|
||||
"contracts/capability/**",
|
||||
"modules/epistemics/**",
|
||||
"modules/capability/**",
|
||||
"adapters/postgres/**",
|
||||
"database/migrations/**",
|
||||
"cmd/core-runtime/**",
|
||||
"cmd/creator-web/**",
|
||||
"cmd/continuity-runtime/**",
|
||||
"deploy/docker-compose.dev.yml",
|
||||
"deploy/docker-compose.prod.yml",
|
||||
"deploy/images/002_owner_passwords.sh",
|
||||
"deploy/windows/**",
|
||||
"tests/owner_acl/**",
|
||||
"tests/cognitive_advisory/**",
|
||||
"docs/PROJECT_STATUS.md",
|
||||
"dev/ACTIVE_TASK.json",
|
||||
"dev/tasks/DEV-000068-runtime-experience-reflection-loop.json"
|
||||
]
|
||||
},
|
||||
"forbidden_change_scope": {
|
||||
"paths": [
|
||||
"architecture/ARCHITECTURE_CONSTITUTION.md",
|
||||
"modules/authority/**",
|
||||
"modules/commit/**",
|
||||
"cmd/commit-runtime/**",
|
||||
"security/**",
|
||||
"ci/**",
|
||||
".gitea/**"
|
||||
]
|
||||
},
|
||||
"allowed_architecture_delta": {
|
||||
"new_cross_module_dependencies": 0,
|
||||
"new_canonical_owners": 0,
|
||||
"new_privileges": 0,
|
||||
"new_third_party_dependencies": 0,
|
||||
"new_provider_adapters": 0,
|
||||
"new_runtime_secret_file_for_existing_owner": "epistemics_role database password for core-runtime"
|
||||
},
|
||||
"contracts": {
|
||||
"consume": [
|
||||
"Interaction HumanUtterance / OriginalHumanIntent / Correction refs",
|
||||
"Continuity HumanResponse / Attention / ProspectiveItem refs",
|
||||
"Capability CapabilityUsageObservation / CapabilityGap / SkillProfile",
|
||||
"Epistemics SystemExpectation / ExperienceRecord / ReflectionCandidate"
|
||||
],
|
||||
"may_change": [
|
||||
"contracts/epistemics@v1",
|
||||
"contracts/capability@v1"
|
||||
]
|
||||
},
|
||||
"semantic_owner": "epistemics",
|
||||
"canonical_state": [
|
||||
"SystemExpectation",
|
||||
"ExperienceRecord",
|
||||
"ReflectionCandidate",
|
||||
"SkillProfile"
|
||||
],
|
||||
"state_transitions": [
|
||||
"meaningful chat help path -> SystemExpectation before outcome",
|
||||
"HumanResponse resolved COMPLETE/SNOOZE/CANCEL -> positive ExperienceRecord and SkillProfile supporting evidence",
|
||||
"Human correction -> USER_CORRECTED ExperienceRecord and ReflectionCandidate",
|
||||
"ambiguous/no-target/unsupported fast path -> decline/clarification ExperienceRecord and ReflectionCandidate",
|
||||
"repeated failure/correction evidence -> SkillProfile DEGRADED/SUSPENDED according to deterministic policy"
|
||||
],
|
||||
"side_effects_and_privileges": [
|
||||
"No external mutation is introduced",
|
||||
"No model output can directly mutate canonical state, Authority, source code, deployment or skill lifecycle",
|
||||
"Core runtime receives only the existing Epistemics owner database credential through secret-file/CredentialRef mechanism",
|
||||
"Creator Web remains a proxy/UI and receives no direct database credential",
|
||||
"Docker-test remains prohibited"
|
||||
],
|
||||
"persistence_and_migrations": [
|
||||
"no new canonical owner",
|
||||
"no cross-owner FK",
|
||||
"no cross-owner SQL join dependency",
|
||||
"reuse DEV-000067 tables with optional indexes/projections only if needed"
|
||||
],
|
||||
"failure_retry_restart": [
|
||||
"Experience/reflection capture is append-only and idempotent by generated IDs",
|
||||
"A failed learning write must not pretend success; production acceptance verifies rows",
|
||||
"Fast-path decline is valid safety behavior and does not mutate target state"
|
||||
],
|
||||
"assumptions": [
|
||||
"DEV-000067 production foundation is active",
|
||||
"ADR-0016 is sufficient; no new architecture owner is required",
|
||||
"V1 uses deterministic reflection triggers; cognitive reflection remains optional candidate generation only"
|
||||
],
|
||||
"acceptance_scenarios": [
|
||||
"Create reminder then complete it -> positive ExperienceRecord and SkillProfile supporting evidence",
|
||||
"Create ambiguous targets and correct Domovoy -> USER_CORRECTED ExperienceRecord and ReflectionCandidate",
|
||||
"Unsupported/uncertain response phrasing -> fast path declines and asks clarification/falls back without guessed mutation",
|
||||
"Operator can inspect recent ExperienceRecords, ReflectionCandidates and SkillProfile status",
|
||||
"Human chat does not expose internal learning IDs",
|
||||
"LLM-free operation passes"
|
||||
],
|
||||
"required_tests": [
|
||||
"go test ./...",
|
||||
"go vet ./...",
|
||||
"go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json",
|
||||
"go run ./cmd/devctl check-architecture",
|
||||
"go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json",
|
||||
"tests/cognitive_advisory/cognitive_worker_env_policy_test.ps1",
|
||||
"tests/owner_acl/verify.ps1 -DockerContext docker-prod -PostgresContainer assistant-postgres-1",
|
||||
"production live learning acceptance A/B/C",
|
||||
"production health",
|
||||
"production /chat verification"
|
||||
],
|
||||
"terminal_gap_states": [
|
||||
"BLOCKED_BY_ARCHITECTURE_GAP",
|
||||
"BLOCKED_BY_SPEC_GAP",
|
||||
"BLOCKED_BY_MISSING_CAPABILITY",
|
||||
"BLOCKED_BY_CONFLICT"
|
||||
]
|
||||
}
|
||||
+43
-4
@@ -1,10 +1,30 @@
|
||||
# Assistant project status
|
||||
|
||||
Audit date: 2026-08-17. Active implementation: DEV-000066 revision 1.
|
||||
Audit date: 2026-08-17. Active implementation: DEV-000067 revision 1.
|
||||
|
||||
## DEV-000067 checkpoint
|
||||
|
||||
- `DEV_000067_STATUS = PRODUCTION_FOUNDATION_ACTIVE`
|
||||
- `LAST_COMPLETED = ADR-0016 accepted; no new Learning owner; Epistemics-owned SystemExpectation / ExperienceRecord / ReflectionCandidate contracts; Capability-owned SkillProfile / FastPathDecision lifecycle contracts; append-only migration 023; owner-local regression tests for positive reminder experience, user correction reflection, fast-path decline, and repeated failure skill degradation`
|
||||
- `ADR_STATUS = ADR-0016 accepted`
|
||||
- `OWNERSHIP_STATUS = no new owner; Experience/Expectation/Reflection belong to Epistemics; Skill/FastPath lifecycle belongs to Capability; Interaction/Continuity/Work/Capability state is referenced by typed refs only`
|
||||
- `LEARNING_BOUNDARY_STATUS = no LearningManager, no self-modifying loop, no prompt optimizer, no automatic source/deployment/Authority mutation`
|
||||
- `EXPERIENCE_STATUS = can represent OriginalHumanIntent, HumanUtterance, context refs, help method, capability usage/gap refs, expectation, actual outcome, HumanResponse/correction refs, verification evidence`
|
||||
- `REFLECTION_STATUS = deterministic ReflectionCandidate foundation exists; future cognitive reflection must route through Cognitive Gateway/Router and produce validated candidates only`
|
||||
- `SKILL_STATUS = SkillProfile lifecycle CANDIDATE/EVALUATING/VALIDATED/DEGRADED/SUSPENDED/RETIRED; fast path can decline with NOT_APPLICABLE/LOW_CONFIDENCE/OUT_OF_DISTRIBUTION/CONFLICT/STALE and return to thinking`
|
||||
- `EVIDENCE_RETENTION_STATUS = skill degradation/replacement preserves supporting and failure experience refs; no source evidence deletion path introduced`
|
||||
- `RUNTIME_STATUS = persistence foundation is present; production runtime credential wiring for Epistemics writes is intentionally not expanded in this checkpoint`
|
||||
- `NOTIFICATION_RECEIVER_STATUS = local receiver endpoint corrected to canonical assistant.cin.su spelling`
|
||||
- `PRODUCTION_SCHEMA_STATUS = migration 023 applied; epistemics system_expectations / experience_records / reflection_candidates present; capability skill_profiles / skill_profile_heads present; seed skill skill-obvious-reminder-response-v1 VALIDATED`
|
||||
- `TESTS_PASS = go test ./...; go vet ./...; go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json; go run ./cmd/devctl check-architecture; go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json; tests/cognitive_advisory/cognitive_worker_env_policy_test.ps1; tests/owner_acl/verify.ps1 -DockerContext docker-prod -PostgresContainer assistant-postgres-1; notification receiver no-display poll; production health; production /chat HTTP 200`
|
||||
- `PRODUCTION_STATUS = healthy`
|
||||
- `PRODUCTION_REVISION = origin/main 2de2885f2f143d41ce0d9cf09ca12a6e5751c52c; Docker runtime artifact not rebuilt because new runtime paths are not activated and migration/script/status were sufficient`
|
||||
- `ACTIVE_COGNITIVE_BINDINGS = none; CPU CANARY unchanged`
|
||||
- `OPENAI_REAL_CREDENTIAL = not required / not activated`
|
||||
|
||||
## DEV-000066 checkpoint
|
||||
|
||||
- `DEV_000066_STATUS = IMPLEMENTED_NOT_PROMOTED`
|
||||
- `DEV_000066_STATUS = PRODUCTION_ACTIVE`
|
||||
- `LAST_COMPLETED = ADR-0015 accepted; Continuity-owned HumanResponse contract foundation; ResponseTargetResolution semantics; append-only human_responses migration; Attention response projection fields; deterministic COMPLETE/SNOOZE/CANCEL/DISMISS interpretation; Creator Web response buttons over ordinary /chat; ActiveProjectFocus leak regression under Interaction semantics`
|
||||
- `ADR_STATUS = ADR-0015 accepted`
|
||||
- `OWNERSHIP_STATUS = no new owner; HumanResponse and response target resolution belong to Continuity; Interaction remains utterance/original-intent owner`
|
||||
@@ -14,8 +34,9 @@ Audit date: 2026-08-17. Active implementation: DEV-000066 revision 1.
|
||||
- `AUTHORITY_STATUS = no new external mutation authority; human reported completion is not verified world truth`
|
||||
- `LLM_FREE_STATUS = obvious response operations are deterministic and do not require cognitive model`
|
||||
- `TESTS_PASS = go test ./...; go vet ./...; go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json; go run ./cmd/devctl check-architecture; go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json; tests/cognitive_advisory/cognitive_worker_env_policy_test.ps1; tests/owner_acl/verify.ps1 -DockerContext docker-prod -PostgresContainer assistant-postgres-1`
|
||||
- `LIVE_ACCEPTANCE = pending production deployment`
|
||||
- `PRODUCTION_STATUS = pending promotion/deployment`
|
||||
- `LIVE_ACCEPTANCE = PASS: external /chat created real reminders; SNOOZE after delivery re-armed the same ProspectiveItem and redelivered once without duplicate AttentionItem; COMPLETE after delivery closed Attention as USER_REPORTED_COMPLETE; CANCEL before delivery suppressed ProspectiveItem and produced no AttentionItem after due window; stale UPO ActiveProjectFocus cleared`
|
||||
- `PRODUCTION_STATUS = healthy`
|
||||
- `PRODUCTION_REVISION = 071adb5ecbf31325678a557c371c512c486d8274`
|
||||
- `ACTIVE_COGNITIVE_BINDINGS = none; CPU CANARY unchanged`
|
||||
- `OPENAI_REAL_CREDENTIAL = not required / not activated`
|
||||
|
||||
@@ -23,6 +44,24 @@ Audit date: 2026-08-17. Active implementation: DEV-000066 revision 1.
|
||||
|
||||
- `DEV_000065_STATUS = COMPLETED`
|
||||
- `LAST_COMPLETED = ADR-0014 accepted; Continuity-owned AttentionItem contract and persistence; due reminder scanner; Commit internal prospective notification endpoint; human-facing Chat / Today / History surface; relative reminder parsing for "через N минут"; deterministic due-delivery regression tests; notification binding origin corrected to assistant.cin.su revision 2`
|
||||
## DEV-000068 checkpoint
|
||||
|
||||
- `DEV_000068_STATUS = COMPLETED`
|
||||
- `LAST_COMPLETED = Runtime Experience + Reflection Loop V1: production runtime now records meaningful ExperienceRecords, deterministic ReflectionCandidates and SkillProfile evidence from /chat, reminder response, correction, capability and fast-path-decline outcomes`
|
||||
- `RUNTIME_EXPERIENCE_LOOP = ACTIVE for production interactions; no LearningManager and no automatic self-modification`
|
||||
- `REFLECTION_STATUS = deterministic candidate generation only; candidates cannot mutate skills, Authority, source code, deployment or canonical lifecycle`
|
||||
- `SKILL_EVIDENCE_STATUS = skill-obvious-reminder-response-v1 accumulates positive/correction/failure evidence from COMPLETE/SNOOZE/CANCEL target-resolution outcomes`
|
||||
- `FAST_PATH_STATUS = uncertain/unsupported continuation can decline and return to clarification/fallback instead of guessed mutation`
|
||||
- `OPERATOR_INSPECTION = Creator Console exposes recent ExperienceRecords, ReflectionCandidates and SkillProfile status through authenticated proxy`
|
||||
- `HUMAN_CHAT_URL = https://assistant.cin.su/chat`
|
||||
- `ACTIVE_COGNITIVE_BINDINGS = none; CPU CANARY unchanged`
|
||||
- `OPENAI_REAL_CREDENTIAL = not required / not activated`
|
||||
- `TESTS_PASS = go test ./...; go vet ./...; go run ./cmd/devctl preflight -active dev/ACTIVE_TASK.json; go run ./cmd/devctl check-architecture; go run ./cmd/devctl check-diff -active dev/ACTIVE_TASK.json; tests/cognitive_advisory/cognitive_worker_env_policy_test.ps1; tests/owner_acl/verify.ps1 -DockerContext docker-prod -PostgresContainer assistant-postgres-1; production artifact confirmation; production health`
|
||||
- `LIVE_ACCEPTANCE = PASS: reminder completion produced positive ExperienceRecord and SkillProfile evidence; explicit correction produced USER_CORRECTED ExperienceRecord and ReflectionCandidate; unsupported/uncertain continuation produced fast-path decline/fallback evidence without guessed mutation; /chat reachable and human-facing replies did not expose learning IDs`
|
||||
- `PRODUCTION_STATUS = healthy`
|
||||
- `PRODUCTION_REVISION = runtime artifact 7fc611ed0898012707fb45841a7a86818144e776`
|
||||
- `NEXT_RECOMMENDED_REVISION = DEV-000069 only after separate approval; no next milestone started automatically`
|
||||
|
||||
- `ADR_STATUS = ADR-0014 accepted`
|
||||
- `OWNERSHIP_STATUS = no new owner; AttentionItem belongs to Continuity; notification delivery remains Commit/Capability/receiver path`
|
||||
- `REMINDER_LIFECYCLE_STATUS = ARMED -> TRIGGERED -> REVALIDATING -> DELIVERED implemented for due scanner`
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
)
|
||||
|
||||
var ErrInvalidSkillProfile = errors.New("invalid skill profile")
|
||||
var ErrInvalidFastPathInput = errors.New("invalid fast path input")
|
||||
|
||||
func ValidateSkillProfile(value capability.SkillProfile) error {
|
||||
if value.ID == "" || value.Revision < 1 || strings.TrimSpace(value.Name) == "" ||
|
||||
strings.TrimSpace(value.Description) == "" || value.Status == "" ||
|
||||
value.ConfidenceMillis < 0 || value.ConfidenceMillis > 1000 || value.RecordedAtUnixMillis < 1 {
|
||||
return ErrInvalidSkillProfile
|
||||
}
|
||||
switch value.Status {
|
||||
case capability.SkillCandidate, capability.SkillEvaluating, capability.SkillValidated, capability.SkillDegraded, capability.SkillSuspended, capability.SkillRetired:
|
||||
default:
|
||||
return ErrInvalidSkillProfile
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ObserveSkillOutcome(profile capability.SkillProfile, observation capability.SkillOutcomeObservation) (capability.SkillProfile, error) {
|
||||
if err := ValidateSkillProfile(profile); err != nil {
|
||||
return capability.SkillProfile{}, err
|
||||
}
|
||||
if observation.ExperienceRef == "" || observation.ObservedAtUnixMillis < 1 {
|
||||
return capability.SkillProfile{}, ErrInvalidSkillProfile
|
||||
}
|
||||
next := profile
|
||||
next.Revision++
|
||||
next.RecordedAtUnixMillis = observation.ObservedAtUnixMillis
|
||||
switch observation.Outcome {
|
||||
case capability.UsageSucceeded:
|
||||
next.SupportingExperienceRefs = appendEvidenceRef(next.SupportingExperienceRefs, observation.ExperienceRef)
|
||||
if next.Status == capability.SkillCandidate || next.Status == capability.SkillEvaluating {
|
||||
next.Status = capability.SkillValidated
|
||||
}
|
||||
next.LastValidatedUnixMillis = observation.ObservedAtUnixMillis
|
||||
next.ConfidenceMillis = clampConfidence(next.ConfidenceMillis + 25)
|
||||
case capability.UsageFailed, capability.UsageUnknown:
|
||||
next.KnownFailureExperienceRefs = appendEvidenceRef(next.KnownFailureExperienceRefs, observation.ExperienceRef)
|
||||
next.ObservedFailureCount++
|
||||
next.ConfidenceMillis = clampConfidence(next.ConfidenceMillis - 150)
|
||||
case capability.UsageSkipped:
|
||||
next.KnownFailureExperienceRefs = appendEvidenceRef(next.KnownFailureExperienceRefs, observation.ExperienceRef)
|
||||
next.ConfidenceMillis = clampConfidence(next.ConfidenceMillis - 25)
|
||||
default:
|
||||
return capability.SkillProfile{}, ErrInvalidSkillProfile
|
||||
}
|
||||
if observation.UserCorrected {
|
||||
next.ObservedCorrectionCount++
|
||||
next.KnownFailureExperienceRefs = appendEvidenceRef(next.KnownFailureExperienceRefs, observation.ExperienceRef)
|
||||
next.ConfidenceMillis = clampConfidence(next.ConfidenceMillis - 200)
|
||||
}
|
||||
if next.Status == capability.SkillValidated && (next.ObservedFailureCount >= 3 || next.ObservedCorrectionCount >= 2 || next.ConfidenceMillis < 500) {
|
||||
next.Status = capability.SkillDegraded
|
||||
}
|
||||
if next.Status == capability.SkillDegraded && next.ConfidenceMillis < 250 {
|
||||
next.Status = capability.SkillSuspended
|
||||
}
|
||||
return next, nil
|
||||
}
|
||||
|
||||
func DecideFastPath(profile capability.SkillProfile, supported bool, confidenceMillis int64, environmentVersionChanged bool, conflict bool, observedAtUnixMillis int64) (capability.FastPathDecision, error) {
|
||||
if err := ValidateSkillProfile(profile); err != nil {
|
||||
return capability.FastPathDecision{}, err
|
||||
}
|
||||
if confidenceMillis < 0 || confidenceMillis > 1000 || observedAtUnixMillis < 1 {
|
||||
return capability.FastPathDecision{}, ErrInvalidFastPathInput
|
||||
}
|
||||
decision := capability.FastPathDecision{
|
||||
SkillProfileID: profile.ID,
|
||||
Decision: capability.FastPathApplied,
|
||||
Explanation: "validated fast path applied",
|
||||
ObservedAtUnixMillis: observedAtUnixMillis,
|
||||
}
|
||||
switch {
|
||||
case profile.Status == capability.SkillSuspended || profile.Status == capability.SkillRetired:
|
||||
return decline(decision, capability.FastPathStale, "skill profile is not eligible for fast-path use"), nil
|
||||
case environmentVersionChanged:
|
||||
return decline(decision, capability.FastPathStale, "environment or implementation version changed since validation"), nil
|
||||
case conflict:
|
||||
return decline(decision, capability.FastPathConflict, "input conflicts with known fast-path assumptions"), nil
|
||||
case !supported:
|
||||
return decline(decision, capability.FastPathOutOfDistribution, "input is outside supported semantics"), nil
|
||||
case confidenceMillis < 700 || profile.ConfidenceMillis < 700:
|
||||
return decline(decision, capability.FastPathLowConfidence, "confidence is too low for optimized path"), nil
|
||||
case profile.Status != capability.SkillValidated:
|
||||
return decline(decision, capability.FastPathNotApplicable, "skill is not validated"), nil
|
||||
default:
|
||||
return decision, nil
|
||||
}
|
||||
}
|
||||
|
||||
func decline(decision capability.FastPathDecision, reason capability.FastPathDeclineReason, explanation string) capability.FastPathDecision {
|
||||
decision.Decision = capability.FastPathEscalate
|
||||
decision.DeclineReason = reason
|
||||
decision.RequiresCognition = true
|
||||
decision.Explanation = explanation
|
||||
return decision
|
||||
}
|
||||
|
||||
func appendEvidenceRef(values []capability.SkillEvidenceRef, ref capability.SkillEvidenceRef) []capability.SkillEvidenceRef {
|
||||
for _, value := range values {
|
||||
if value == ref {
|
||||
return values
|
||||
}
|
||||
}
|
||||
return append(values, ref)
|
||||
}
|
||||
|
||||
func clampConfidence(value int64) int64 {
|
||||
if value < 0 {
|
||||
return 0
|
||||
}
|
||||
if value > 1000 {
|
||||
return 1000
|
||||
}
|
||||
return value
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/capability"
|
||||
)
|
||||
|
||||
func TestRepeatedCapabilityFailuresDegradeSkillWithoutDeletingHistory(t *testing.T) {
|
||||
profile := skillProfile()
|
||||
profile.SupportingExperienceRefs = []capability.SkillEvidenceRef{"experience:success-1"}
|
||||
for _, ref := range []capability.SkillEvidenceRef{"experience:failure-1", "experience:failure-2", "experience:failure-3"} {
|
||||
next, err := ObserveSkillOutcome(profile, capability.SkillOutcomeObservation{ExperienceRef: ref, Outcome: capability.UsageFailed, ObservedAtUnixMillis: profile.RecordedAtUnixMillis + 100})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
profile = next
|
||||
}
|
||||
if profile.Status != capability.SkillDegraded {
|
||||
t.Fatalf("expected degraded skill, got %#v", profile)
|
||||
}
|
||||
if len(profile.SupportingExperienceRefs) != 1 || len(profile.KnownFailureExperienceRefs) != 3 {
|
||||
t.Fatalf("skill degradation must preserve success/failure evidence: %#v", profile)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFastPathDeclinesUnsupportedInputAndEscalatesToThinking(t *testing.T) {
|
||||
decision, err := DecideFastPath(skillProfile(), false, 900, false, false, 2000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if decision.Decision != capability.FastPathEscalate || decision.DeclineReason != capability.FastPathOutOfDistribution || !decision.RequiresCognition {
|
||||
t.Fatalf("unexpected fast path decision: %#v", decision)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatedSkillCanApplyFastPathWhenFreshAndConfident(t *testing.T) {
|
||||
decision, err := DecideFastPath(skillProfile(), true, 950, false, false, 2000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if decision.Decision != capability.FastPathApplied || decision.RequiresCognition {
|
||||
t.Fatalf("unexpected fast path decision: %#v", decision)
|
||||
}
|
||||
}
|
||||
|
||||
func skillProfile() capability.SkillProfile {
|
||||
return capability.SkillProfile{
|
||||
ID: "skill-obvious-reminder-response-v1",
|
||||
Revision: 1,
|
||||
Name: "Resolve obvious reminder continuation",
|
||||
Description: "Use a recent unambiguous reminder/attention target for complete/snooze/cancel without cognition.",
|
||||
ApplicableContext: []string{"continuity:reminder-response"},
|
||||
ImplementationRef: "continuity:human-response-target-resolver-v1",
|
||||
ImplementationVersion: "1",
|
||||
Status: capability.SkillValidated,
|
||||
ConfidenceMillis: 850,
|
||||
LastValidatedUnixMillis: 1000,
|
||||
RecordedAtUnixMillis: 1000,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||
"git.cin.su/m/Assistant/platform/ids"
|
||||
)
|
||||
|
||||
var ErrInvalidExperience = errors.New("invalid experience")
|
||||
var ErrInvalidExpectation = errors.New("invalid expectation")
|
||||
var ErrInvalidReflectionCandidate = errors.New("invalid reflection candidate")
|
||||
|
||||
func NewSystemExpectation(value epistemics.SystemExpectation) (epistemics.SystemExpectation, error) {
|
||||
if value.ID == "" {
|
||||
id, err := ids.New()
|
||||
if err != nil {
|
||||
return epistemics.SystemExpectation{}, err
|
||||
}
|
||||
value.ID = epistemics.SystemExpectationID(id)
|
||||
}
|
||||
if value.OriginalIntentID == "" || value.SourceUtteranceID == "" || value.ExpectedOutcome == "" ||
|
||||
strings.TrimSpace(value.ExpectedEffect) == "" || value.ConfidenceMillis < 0 || value.ConfidenceMillis > 1000 ||
|
||||
value.RecordedAtUnixMillis < 1 {
|
||||
return epistemics.SystemExpectation{}, ErrInvalidExpectation
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func NewExperienceRecord(value epistemics.ExperienceRecord) (epistemics.ExperienceRecord, error) {
|
||||
if value.ID == "" {
|
||||
id, err := ids.New()
|
||||
if err != nil {
|
||||
return epistemics.ExperienceRecord{}, err
|
||||
}
|
||||
value.ID = epistemics.ExperienceRecordID(id)
|
||||
}
|
||||
if value.SourceKind == "" || value.OriginalIntentID == "" || value.SourceUtteranceID == "" ||
|
||||
value.HelpMethod == "" || value.ExpectedOutcome == "" || value.ActualOutcome == "" ||
|
||||
value.ConfidenceMillis < 0 || value.ConfidenceMillis > 1000 || value.RecordedAtUnixMillis < 1 {
|
||||
return epistemics.ExperienceRecord{}, ErrInvalidExperience
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func ReflectionForExperience(value epistemics.ExperienceRecord) (epistemics.ReflectionCandidate, bool, error) {
|
||||
if _, err := NewExperienceRecord(value); err != nil {
|
||||
return epistemics.ReflectionCandidate{}, false, err
|
||||
}
|
||||
trigger, ok := reflectionTrigger(value)
|
||||
if !ok {
|
||||
return epistemics.ReflectionCandidate{}, false, nil
|
||||
}
|
||||
id, err := ids.New()
|
||||
if err != nil {
|
||||
return epistemics.ReflectionCandidate{}, false, err
|
||||
}
|
||||
candidate := epistemics.ReflectionCandidate{
|
||||
ID: epistemics.ReflectionCandidateID(id),
|
||||
Trigger: trigger,
|
||||
State: epistemics.ReflectionCandidateOpen,
|
||||
SourceExperienceID: value.ID,
|
||||
OriginalIntentID: value.OriginalIntentID,
|
||||
SourceUtteranceID: value.SourceUtteranceID,
|
||||
Summary: reflectionSummary(trigger, value),
|
||||
EvidenceRefs: reflectionEvidence(value),
|
||||
RequiresCognition: trigger == epistemics.ReflectionContradiction || trigger == epistemics.ReflectionRepeatedDegradation,
|
||||
RecordedAtUnixMillis: value.RecordedAtUnixMillis,
|
||||
Revision: 1,
|
||||
}
|
||||
if err := ValidateReflectionCandidate(candidate); err != nil {
|
||||
return epistemics.ReflectionCandidate{}, false, err
|
||||
}
|
||||
return candidate, true, nil
|
||||
}
|
||||
|
||||
func ValidateReflectionCandidate(value epistemics.ReflectionCandidate) error {
|
||||
if value.ID == "" || value.Trigger == "" || value.State == "" || value.SourceExperienceID == "" ||
|
||||
value.OriginalIntentID == "" || value.SourceUtteranceID == "" || strings.TrimSpace(value.Summary) == "" ||
|
||||
value.RecordedAtUnixMillis < 1 || value.Revision < 1 {
|
||||
return ErrInvalidReflectionCandidate
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func FastPathDeclineExperience(intent epistemics.OriginalIntentRef, utterance epistemics.SourceUtteranceRef, skill epistemics.SkillRef, reason string, millis int64) (epistemics.ExperienceRecord, error) {
|
||||
outcome := epistemics.OutcomeNotApplicable
|
||||
switch reason {
|
||||
case string(epistemics.OutcomeLowConfidence):
|
||||
outcome = epistemics.OutcomeLowConfidence
|
||||
case string(epistemics.OutcomeOutOfDistribution):
|
||||
outcome = epistemics.OutcomeOutOfDistribution
|
||||
case string(epistemics.OutcomeConflict):
|
||||
outcome = epistemics.OutcomeConflict
|
||||
case string(epistemics.OutcomeStale):
|
||||
outcome = epistemics.OutcomeStale
|
||||
}
|
||||
return NewExperienceRecord(epistemics.ExperienceRecord{
|
||||
SourceKind: epistemics.ExperienceSourceInteraction,
|
||||
OriginalIntentID: intent,
|
||||
SourceUtteranceID: utterance,
|
||||
HelpMethod: epistemics.HelpMethodDeterministicFastPath,
|
||||
SkillRef: skill,
|
||||
ExpectedOutcome: epistemics.OutcomeSuccess,
|
||||
ActualOutcome: outcome,
|
||||
OutcomeDetail: "fast path declined and returned to broader reasoning: " + reason,
|
||||
ConfidenceMillis: 1000,
|
||||
RecordedAtUnixMillis: millis,
|
||||
VerificationEvidence: []epistemics.EvidenceRef{"fast-path-decline"},
|
||||
})
|
||||
}
|
||||
|
||||
func reflectionTrigger(value epistemics.ExperienceRecord) (epistemics.ReflectionTriggerKind, bool) {
|
||||
switch value.ActualOutcome {
|
||||
case epistemics.OutcomeUserCorrected:
|
||||
return epistemics.ReflectionUserCorrection, true
|
||||
case epistemics.OutcomeUnexpected:
|
||||
return epistemics.ReflectionUnexpectedOutcome, true
|
||||
case epistemics.OutcomeFailed:
|
||||
return epistemics.ReflectionFailure, true
|
||||
case epistemics.OutcomeLowConfidence:
|
||||
return epistemics.ReflectionLowConfidence, true
|
||||
case epistemics.OutcomeOutOfDistribution, epistemics.OutcomeConflict, epistemics.OutcomeStale:
|
||||
return epistemics.ReflectionSampledAudit, true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func reflectionSummary(trigger epistemics.ReflectionTriggerKind, value epistemics.ExperienceRecord) string {
|
||||
if value.OutcomeDetail != "" {
|
||||
return string(trigger) + ": " + value.OutcomeDetail
|
||||
}
|
||||
return string(trigger) + " for experience " + string(value.ID)
|
||||
}
|
||||
|
||||
func reflectionEvidence(value epistemics.ExperienceRecord) []epistemics.EvidenceRef {
|
||||
refs := append([]epistemics.EvidenceRef{}, value.VerificationEvidence...)
|
||||
if value.HumanResponseRef != "" {
|
||||
refs = append(refs, epistemics.EvidenceRef("continuity:human-response:"+value.HumanResponseRef))
|
||||
}
|
||||
if value.CorrectionUtteranceID != "" {
|
||||
refs = append(refs, epistemics.EvidenceRef("interaction:correction:"+value.CorrectionUtteranceID))
|
||||
}
|
||||
if value.CapabilityUsageObservationRef != "" {
|
||||
refs = append(refs, epistemics.EvidenceRef("capability:usage:"+value.CapabilityUsageObservationRef))
|
||||
}
|
||||
if value.CapabilityGapRef != "" {
|
||||
refs = append(refs, epistemics.EvidenceRef("capability:gap:"+value.CapabilityGapRef))
|
||||
}
|
||||
return refs
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package domain
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.cin.su/m/Assistant/contracts/epistemics"
|
||||
)
|
||||
|
||||
func TestAcceptedReminderResponseCreatesPositiveExperienceWithoutReflection(t *testing.T) {
|
||||
experience, err := NewExperienceRecord(epistemics.ExperienceRecord{
|
||||
SourceKind: epistemics.ExperienceSourceContinuity,
|
||||
OriginalIntentID: "intent-1",
|
||||
SourceUtteranceID: "utterance-1",
|
||||
ContextRefs: []epistemics.ContextRef{"continuity:attention:attention-1"},
|
||||
HelpMethod: epistemics.HelpMethodDeterministicFastPath,
|
||||
SkillRef: "skill:obvious-reminder-response-v1",
|
||||
ExpectedOutcome: epistemics.OutcomeSuccess,
|
||||
ActualOutcome: epistemics.OutcomeSuccess,
|
||||
OutcomeRef: "continuity:prospective:p1",
|
||||
HumanResponseRef: "response-1",
|
||||
VerificationEvidence: []epistemics.EvidenceRef{"target-resolver:single-recent-reminder", "human-response:complete"},
|
||||
ConfidenceMillis: 950,
|
||||
RecordedAtUnixMillis: 1000,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, ok, err := ReflectionForExperience(experience); err != nil || ok {
|
||||
t.Fatalf("positive accepted experience should not force reflection: ok=%v err=%v", ok, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUserCorrectionCreatesReflectionCandidate(t *testing.T) {
|
||||
experience, err := NewExperienceRecord(epistemics.ExperienceRecord{
|
||||
SourceKind: epistemics.ExperienceSourceInteraction,
|
||||
OriginalIntentID: "intent-1",
|
||||
SourceUtteranceID: "utterance-2",
|
||||
HelpMethod: epistemics.HelpMethodClarification,
|
||||
ExpectedOutcome: epistemics.OutcomeSuccess,
|
||||
ActualOutcome: epistemics.OutcomeUserCorrected,
|
||||
OutcomeDetail: "user corrected wrong target/project interpretation",
|
||||
CorrectionUtteranceID: "utterance-2",
|
||||
ConfidenceMillis: 1000,
|
||||
RecordedAtUnixMillis: 1100,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
candidate, ok, err := ReflectionForExperience(experience)
|
||||
if err != nil || !ok {
|
||||
t.Fatalf("expected reflection candidate, ok=%v err=%v", ok, err)
|
||||
}
|
||||
if candidate.Trigger != epistemics.ReflectionUserCorrection || candidate.State != epistemics.ReflectionCandidateOpen || len(candidate.EvidenceRefs) == 0 {
|
||||
t.Fatalf("unexpected reflection candidate: %#v", candidate)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFastPathDeclineReturnsToThinkingInsteadOfGuessing(t *testing.T) {
|
||||
experience, err := FastPathDeclineExperience("intent-1", "utterance-1", "skill:obvious-reminder-response-v1", string(epistemics.OutcomeOutOfDistribution), 1200)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if experience.ActualOutcome != epistemics.OutcomeOutOfDistribution || experience.HelpMethod != epistemics.HelpMethodDeterministicFastPath {
|
||||
t.Fatalf("unexpected fast path decline experience: %#v", experience)
|
||||
}
|
||||
candidate, ok, err := ReflectionForExperience(experience)
|
||||
if err != nil || !ok || candidate.Trigger != epistemics.ReflectionSampledAudit {
|
||||
t.Fatalf("decline should preserve reflection/escalation evidence, candidate=%#v ok=%v err=%v", candidate, ok, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user