Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a862a07ae1 | ||
|
|
4d63c1bea4 | ||
|
|
91c38de167 | ||
|
|
926afc82b5 | ||
|
|
4a066d71f4 | ||
|
|
7fc611ed08 | ||
|
|
924d8c3767 | ||
|
|
67ff1f73cd | ||
|
|
0ad247bae3 | ||
|
|
2de2885f2f | ||
|
|
069c3837f3 | ||
|
|
8a88565772 |
@@ -11,7 +11,7 @@ import (
|
|||||||
type AuthorityRepository struct{ pool *pgxpool.Pool }
|
type AuthorityRepository struct{ pool *pgxpool.Pool }
|
||||||
|
|
||||||
func OpenAuthority(ctx context.Context, c ContinuityConfig) (*AuthorityRepository, error) {
|
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 {
|
if e != nil {
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ var ErrCapabilityImplementationNotFound = errors.New("capability implementation
|
|||||||
type CapabilityRepository struct{ pool *pgxpool.Pool }
|
type CapabilityRepository struct{ pool *pgxpool.Pool }
|
||||||
|
|
||||||
func OpenCapability(ctx context.Context, config ContinuityConfig) (*CapabilityRepository, error) {
|
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 := openPool(ctx, config)
|
||||||
pool, err := pgxpool.New(ctx, dsn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -246,6 +245,53 @@ func (r *CapabilityRepository) SaveSkillProfile(ctx context.Context, value capab
|
|||||||
return tx.Commit(ctx)
|
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) {
|
func (r *CapabilityRepository) LoadBinding(ctx context.Context, id capability.CapabilityBindingID, revision int64) (capability.Binding, error) {
|
||||||
var binding capability.Binding
|
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,
|
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 }
|
type CommitRepository struct{ pool *pgxpool.Pool }
|
||||||
|
|
||||||
func OpenCommit(ctx context.Context, c ContinuityConfig) (*CommitRepository, error) {
|
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 {
|
if e != nil {
|
||||||
return nil, e
|
return nil, e
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"git.cin.su/m/Assistant/contracts/authority"
|
"git.cin.su/m/Assistant/contracts/authority"
|
||||||
"git.cin.su/m/Assistant/contracts/continuity"
|
"git.cin.su/m/Assistant/contracts/continuity"
|
||||||
@@ -29,9 +30,25 @@ var (
|
|||||||
type ContinuityConfig struct{ Host, Port, Database, User, Password string }
|
type ContinuityConfig struct{ Host, Port, Database, User, Password string }
|
||||||
type ContinuityRepository struct{ pool *pgxpool.Pool }
|
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) {
|
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 := openPool(ctx, config)
|
||||||
pool, err := pgxpool.New(ctx, dsn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ import (
|
|||||||
type EpistemicsRepository struct{ pool *pgxpool.Pool }
|
type EpistemicsRepository struct{ pool *pgxpool.Pool }
|
||||||
|
|
||||||
func OpenEpistemics(ctx context.Context, config ContinuityConfig) (*EpistemicsRepository, error) {
|
func OpenEpistemics(ctx context.Context, config ContinuityConfig) (*EpistemicsRepository, 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 := openPool(ctx, config)
|
||||||
pool, err := pgxpool.New(ctx, dsn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -31,6 +30,58 @@ func (r *EpistemicsRepository) SaveSystemExpectation(ctx context.Context, value
|
|||||||
return err
|
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 {
|
func (r *EpistemicsRepository) SaveExperienceRecord(ctx context.Context, value epistemics.ExperienceRecord) error {
|
||||||
_, err := r.pool.Exec(ctx, `INSERT INTO epistemics.experience_records (
|
_, 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,
|
experience_id, source_kind, original_intent_id, source_utterance_id, context_refs, help_method, help_method_ref,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
type IdentityRepository struct{ pool *pgxpool.Pool }
|
type IdentityRepository struct{ pool *pgxpool.Pool }
|
||||||
|
|
||||||
func OpenIdentity(ctx context.Context, config ContinuityConfig) (*IdentityRepository, error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package postgres
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"git.cin.su/m/Assistant/contracts/interaction"
|
"git.cin.su/m/Assistant/contracts/interaction"
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
@@ -11,7 +10,7 @@ import (
|
|||||||
type InteractionRepository struct{ pool *pgxpool.Pool }
|
type InteractionRepository struct{ pool *pgxpool.Pool }
|
||||||
|
|
||||||
func OpenInteraction(ctx context.Context, c ContinuityConfig) (*InteractionRepository, error) {
|
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
|
return &InteractionRepository{p}, e
|
||||||
}
|
}
|
||||||
func (r *InteractionRepository) Close() { r.pool.Close() }
|
func (r *InteractionRepository) Close() { r.pool.Close() }
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package postgres
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"git.cin.su/m/Assistant/contracts/identity"
|
"git.cin.su/m/Assistant/contracts/identity"
|
||||||
@@ -13,8 +12,7 @@ import (
|
|||||||
type SituationRepository struct{ pool *pgxpool.Pool }
|
type SituationRepository struct{ pool *pgxpool.Pool }
|
||||||
|
|
||||||
func OpenSituation(ctx context.Context, config ContinuityConfig) (*SituationRepository, error) {
|
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 := openPool(ctx, config)
|
||||||
pool, err := pgxpool.New(ctx, dsn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ import (
|
|||||||
type WorkRepository struct{ pool *pgxpool.Pool }
|
type WorkRepository struct{ pool *pgxpool.Pool }
|
||||||
|
|
||||||
func OpenWork(ctx context.Context, config ContinuityConfig) (*WorkRepository, error) {
|
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 := openPool(ctx, config)
|
||||||
pool, err := pgxpool.New(ctx, dsn)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -144,10 +144,10 @@ func resolveChatCapability(ctx context.Context, repository *postgres.CapabilityR
|
|||||||
return resolution, err
|
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()
|
id, err := ids.New()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return "", err
|
||||||
}
|
}
|
||||||
latency := now.Sub(started).Milliseconds()
|
latency := now.Sub(started).Milliseconds()
|
||||||
observation := capability.CapabilityUsageObservation{
|
observation := capability.CapabilityUsageObservation{
|
||||||
@@ -164,7 +164,10 @@ func observeCapabilityUsage(ctx context.Context, repository *postgres.Capability
|
|||||||
LatencyMillis: &latency,
|
LatencyMillis: &latency,
|
||||||
ObservedAtUnixMillis: now.UnixMilli(),
|
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) {
|
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)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
defer capabilityRepository.Close()
|
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")})
|
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 {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@@ -94,6 +107,7 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
learning := learningRecorder{epistemics: epistemicsRepository, capability: capabilityRepository}
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
authenticateCreator := func(w http.ResponseWriter, r *http.Request) (identity.CreatorSession, bool) {
|
authenticateCreator := func(w http.ResponseWriter, r *http.Request) (identity.CreatorSession, bool) {
|
||||||
session, err := identitydomain.ValidateCreatorSession(sessionKey, bearerToken(r), time.Now())
|
session, err := identitydomain.ValidateCreatorSession(sessionKey, bearerToken(r), time.Now())
|
||||||
@@ -172,6 +186,45 @@ func main() {
|
|||||||
w.Header().Set("Content-Type", "application/json")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
_ = json.NewEncoder(w).Encode(status)
|
_ = 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) {
|
mux.HandleFunc("POST /v1/creator/chat/messages", func(w http.ResponseWriter, r *http.Request) {
|
||||||
session, ok := authenticateCreator(w, r)
|
session, ok := authenticateCreator(w, r)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -245,6 +298,9 @@ func main() {
|
|||||||
Message: utterance.Content,
|
Message: utterance.Content,
|
||||||
RequestedAtUnixMillis: now.UnixMilli(),
|
RequestedAtUnixMillis: now.UnixMilli(),
|
||||||
})
|
})
|
||||||
|
if semantic.Interpretation.SpeechAct == interaction.SpeechActCorrection {
|
||||||
|
learning.recordCorrection(r.Context(), correctionLearningInput{Utterance: utterance, Intent: intent, At: now})
|
||||||
|
}
|
||||||
activeFocusName := ""
|
activeFocusName := ""
|
||||||
activeFocusProvenance := situation.ProjectResolutionProvenance("")
|
activeFocusProvenance := situation.ProjectResolutionProvenance("")
|
||||||
if semantic.ActiveFocus != nil {
|
if semantic.ActiveFocus != nil {
|
||||||
@@ -270,6 +326,9 @@ func main() {
|
|||||||
if responseErr == nil && responseResult.Handled {
|
if responseErr == nil && responseResult.Handled {
|
||||||
planningReply = responseResult.Reply
|
planningReply = responseResult.Reply
|
||||||
needsCognition = false
|
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{
|
planningInput := work.PlanningInput{
|
||||||
ConversationID: work.ConversationRef(conversation.ID),
|
ConversationID: work.ConversationRef(conversation.ID),
|
||||||
@@ -320,10 +379,12 @@ func main() {
|
|||||||
if scheduleErr == nil {
|
if scheduleErr == nil {
|
||||||
reminder = &result.Item
|
reminder = &result.Item
|
||||||
planningReply = fmt.Sprintf("Готово. Я создал напоминание: %s.", result.Item.Summary)
|
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 {
|
} else {
|
||||||
planningReply = "Scheduling capability сейчас недоступна: напоминание не создано. Я не буду притворяться, что действие выполнено."
|
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 {
|
} else {
|
||||||
planningReply = "Мне нужно уточнить время напоминания. Например: «Напомни завтра в 10 позвонить врачу»."
|
planningReply = "Мне нужно уточнить время напоминания. Например: «Напомни завтра в 10 позвонить врачу»."
|
||||||
@@ -332,6 +393,7 @@ func main() {
|
|||||||
} else if resolution.Gap != nil {
|
} else if resolution.Gap != nil {
|
||||||
capabilityGapID = resolution.Gap.ID
|
capabilityGapID = resolution.Gap.ID
|
||||||
planningReply = "Сейчас scheduling capability недоступна, поэтому напоминание не создано. Я зафиксировал capability gap."
|
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
|
needsCognition = false
|
||||||
}
|
}
|
||||||
@@ -344,6 +406,7 @@ func main() {
|
|||||||
} else if resolution.Gap != nil {
|
} else if resolution.Gap != nil {
|
||||||
capabilityGapID = resolution.Gap.ID
|
capabilityGapID = resolution.Gap.ID
|
||||||
planningReply = "Сейчас у меня ещё не подключена возможность отправлять почту. Я зафиксировал capability gap, без ложного выполнения."
|
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
|
needsCognition = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -536,6 +536,8 @@ func creatorRoute(method, path string) (routeTarget, string, bool) {
|
|||||||
if validID(id) {
|
if validID(id) {
|
||||||
return targetCore, "/v1/creator/capabilities/" + id + "/health", true
|
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/"):
|
case method == http.MethodGet && strings.HasPrefix(path, "/api/prospective-items/"):
|
||||||
id := strings.TrimPrefix(path, "/api/prospective-items/")
|
id := strings.TrimPrefix(path, "/api/prospective-items/")
|
||||||
if validID(id) {
|
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; }
|
.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); }
|
.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 { 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; }
|
.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 h2 { margin: 0; font: 400 22px/1 var(--serif); }
|
.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 { 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-tools .icon-button { color: var(--muted); font-size: 18px; }
|
||||||
.activity-list { max-height: 300px; overflow: auto; scrollbar-color: var(--faint) transparent; }
|
.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-reconcile:disabled { opacity: .55; cursor: wait; }
|
||||||
.activity-error { color: var(--bad); }
|
.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-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; }
|
.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; }
|
.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; }
|
.advisor-result { grid-template-columns: 1fr; gap: 10px; }
|
||||||
.register-panel > header { align-items: start; }
|
.register-panel > header { align-items: start; }
|
||||||
.activity-panel > header { align-items: flex-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; }
|
.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-row { padding: 14px 16px; grid-template-columns: minmax(0, 1fr) auto; gap: 8px 12px; }
|
||||||
.activity-state { grid-column: 2; grid-row: 1; }
|
.activity-state { grid-column: 2; grid-row: 1; }
|
||||||
.activity-operation { grid-column: 1; grid-row: 1; }
|
.activity-operation { grid-column: 1; grid-row: 1; }
|
||||||
|
|||||||
@@ -84,7 +84,10 @@ async function showConsole() {
|
|||||||
await loadAdvisoryPolicy();
|
await loadAdvisoryPolicy();
|
||||||
const context = saveContext();
|
const context = saveContext();
|
||||||
if (context["session-id"] && context["workspace-id"]) loadDashboard();
|
if (context["session-id"] && context["workspace-id"]) loadDashboard();
|
||||||
else loadActionHistory();
|
else {
|
||||||
|
loadActionHistory();
|
||||||
|
loadLearning();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function endSession(message = "Сессия завершена.") {
|
function endSession(message = "Сессия завершена.") {
|
||||||
@@ -147,6 +150,7 @@ contextForm.addEventListener("submit", (event) => {
|
|||||||
|
|
||||||
byId("refresh-all").addEventListener("click", loadDashboard);
|
byId("refresh-all").addEventListener("click", loadDashboard);
|
||||||
byId("refresh-actions").addEventListener("click", loadActionHistory);
|
byId("refresh-actions").addEventListener("click", loadActionHistory);
|
||||||
|
byId("refresh-learning").addEventListener("click", loadLearning);
|
||||||
byId("logout").addEventListener("click", () => endSession());
|
byId("logout").addEventListener("click", () => endSession());
|
||||||
|
|
||||||
advisoryForm.addEventListener("submit", async (event) => {
|
advisoryForm.addEventListener("submit", async (event) => {
|
||||||
@@ -234,6 +238,7 @@ async function loadDashboard() {
|
|||||||
}
|
}
|
||||||
work.push(loadProspectiveRegister(context["prospective-id"]));
|
work.push(loadProspectiveRegister(context["prospective-id"]));
|
||||||
work.push(loadActionHistory());
|
work.push(loadActionHistory());
|
||||||
|
work.push(loadLearning());
|
||||||
if (context["capability-id"]) work.push(loadCapability(context["capability-id"]));
|
if (context["capability-id"]) work.push(loadCapability(context["capability-id"]));
|
||||||
else setSignal("capability", "не выбрана", "idle");
|
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())}`);
|
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() {
|
async function loadActionHistory() {
|
||||||
const list = byId("action-history");
|
const list = byId("action-history");
|
||||||
list.setAttribute("aria-busy", "true");
|
list.setAttribute("aria-busy", "true");
|
||||||
|
|||||||
@@ -143,6 +143,30 @@
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</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">
|
<section class="data-grid">
|
||||||
<article class="data-card focus-card">
|
<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>
|
<header><span class="card-number">01</span><div><p>Situation</p><h2>Активный фокус</h2></div><span id="focus-badge" class="status-badge neutral">НЕ ЗАГРУЖЕН</span></header>
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ services:
|
|||||||
SITUATION_DB_PASSWORD: ${SITUATION_DB_PASSWORD:-assistant_situation_dev_only}
|
SITUATION_DB_PASSWORD: ${SITUATION_DB_PASSWORD:-assistant_situation_dev_only}
|
||||||
WORK_DB_PASSWORD: ${WORK_DB_PASSWORD:-assistant_work_dev_only}
|
WORK_DB_PASSWORD: ${WORK_DB_PASSWORD:-assistant_work_dev_only}
|
||||||
CAPABILITY_DB_PASSWORD: ${CAPABILITY_DB_PASSWORD:-assistant_capability_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}
|
COMMIT_DB_PASSWORD: ${COMMIT_DB_PASSWORD:-assistant_commit_dev_only}
|
||||||
ports: ["15432:5432"]
|
ports: ["15432:5432"]
|
||||||
volumes:
|
volumes:
|
||||||
@@ -50,6 +51,8 @@ services:
|
|||||||
WORK_POSTGRES_PASSWORD: ${WORK_DB_PASSWORD:-assistant_work_dev_only}
|
WORK_POSTGRES_PASSWORD: ${WORK_DB_PASSWORD:-assistant_work_dev_only}
|
||||||
CAPABILITY_POSTGRES_USER: capability_role
|
CAPABILITY_POSTGRES_USER: capability_role
|
||||||
CAPABILITY_POSTGRES_PASSWORD: ${CAPABILITY_DB_PASSWORD:-assistant_capability_dev_only}
|
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"]
|
ports: ["19080:8080"]
|
||||||
depends_on:
|
depends_on:
|
||||||
postgres: { condition: service_healthy }
|
postgres: { condition: service_healthy }
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ services:
|
|||||||
WORK_DB_PASSWORD: ${WORK_DB_PASSWORD}
|
WORK_DB_PASSWORD: ${WORK_DB_PASSWORD}
|
||||||
CAPABILITY_DB_PASSWORD: ${CAPABILITY_DB_PASSWORD}
|
CAPABILITY_DB_PASSWORD: ${CAPABILITY_DB_PASSWORD}
|
||||||
COMMIT_DB_PASSWORD: ${COMMIT_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
|
INTERACTION_DB_PASSWORD_FILE: /run/secrets/interaction_db_password
|
||||||
volumes:
|
volumes:
|
||||||
- assistant_pg:/var/lib/postgresql
|
- assistant_pg:/var/lib/postgresql
|
||||||
@@ -72,6 +73,9 @@ services:
|
|||||||
WORK_POSTGRES_PASSWORD: ${WORK_DB_PASSWORD}
|
WORK_POSTGRES_PASSWORD: ${WORK_DB_PASSWORD}
|
||||||
CAPABILITY_POSTGRES_USER: capability_role
|
CAPABILITY_POSTGRES_USER: capability_role
|
||||||
CAPABILITY_POSTGRES_PASSWORD: ${CAPABILITY_DB_PASSWORD}
|
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_USER: interaction_role
|
||||||
INTERACTION_POSTGRES_CREDENTIAL_REF: credential://domovoy/interaction/db-password/v1
|
INTERACTION_POSTGRES_CREDENTIAL_REF: credential://domovoy/interaction/db-password/v1
|
||||||
INTERACTION_POSTGRES_PASSWORD_FILE: /run/secrets/interaction_db_password
|
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'
|
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';
|
ALTER ROLE capability_role PASSWORD :'capability_password';
|
||||||
SQL
|
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'
|
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';
|
ALTER ROLE commit_role PASSWORD :'commit_password';
|
||||||
SQL
|
SQL
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
param(
|
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]$KeyRecordPath = "$env:LOCALAPPDATA\Domovoy\notification-receiver-read-key.dpapi.json",
|
||||||
[string]$StatePath = "$env:LOCALAPPDATA\Domovoy\notification-receiver-state.json",
|
[string]$StatePath = "$env:LOCALAPPDATA\Domovoy\notification-receiver-state.json",
|
||||||
[ValidateRange(1, 5)]
|
[ValidateRange(1, 5)]
|
||||||
@@ -24,7 +24,7 @@ function Write-AtomicJson {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$uri = [Uri]$Endpoint
|
$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'
|
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" }
|
if (-not (Test-Path -LiteralPath $KeyRecordPath -PathType Leaf)) { throw "Notification receiver key not found: $KeyRecordPath" }
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"task_file": "dev/tasks/DEV-000067-experience-reflection-skill-evolution.json",
|
"task_file": "dev/tasks/DEV-000068-runtime-experience-reflection-loop.json",
|
||||||
"task_id": "DEV-000067",
|
"task_id": "DEV-000068",
|
||||||
"task_revision": 1
|
"task_revision": 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,7 @@
|
|||||||
"database/migrations/**",
|
"database/migrations/**",
|
||||||
"cmd/core-runtime/**",
|
"cmd/core-runtime/**",
|
||||||
"cmd/creator-web/**",
|
"cmd/creator-web/**",
|
||||||
|
"deploy/windows/Invoke-DomovoyNotificationReceiver.ps1",
|
||||||
"tests/owner_acl/**",
|
"tests/owner_acl/**",
|
||||||
"tests/cognitive_advisory/**",
|
"tests/cognitive_advisory/**",
|
||||||
"docs/PROJECT_STATUS.md",
|
"docs/PROJECT_STATUS.md",
|
||||||
@@ -126,6 +127,8 @@
|
|||||||
"tests/cognitive_advisory/cognitive_worker_env_policy_test.ps1",
|
"tests/cognitive_advisory/cognitive_worker_env_policy_test.ps1",
|
||||||
"tests/owner_acl/verify.ps1 -DockerContext docker-prod -PostgresContainer assistant-postgres-1",
|
"tests/owner_acl/verify.ps1 -DockerContext docker-prod -PostgresContainer assistant-postgres-1",
|
||||||
"production /chat verification if runtime deployment is required"
|
"production /chat verification if runtime deployment is required"
|
||||||
|
,
|
||||||
|
"notification receiver endpoint uses canonical assistant.cin.su spelling"
|
||||||
],
|
],
|
||||||
"terminal_gap_states": [
|
"terminal_gap_states": [
|
||||||
"BLOCKED_BY_ARCHITECTURE_GAP",
|
"BLOCKED_BY_ARCHITECTURE_GAP",
|
||||||
|
|||||||
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
+24
-1
@@ -4,7 +4,7 @@ Audit date: 2026-08-17. Active implementation: DEV-000067 revision 1.
|
|||||||
|
|
||||||
## DEV-000067 checkpoint
|
## DEV-000067 checkpoint
|
||||||
|
|
||||||
- `DEV_000067_STATUS = IN_PROGRESS_LOCAL_FOUNDATION`
|
- `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`
|
- `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`
|
- `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`
|
- `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`
|
||||||
@@ -14,6 +14,11 @@ Audit date: 2026-08-17. Active implementation: DEV-000067 revision 1.
|
|||||||
- `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`
|
- `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`
|
- `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`
|
- `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`
|
- `ACTIVE_COGNITIVE_BINDINGS = none; CPU CANARY unchanged`
|
||||||
- `OPENAI_REAL_CREDENTIAL = not required / not activated`
|
- `OPENAI_REAL_CREDENTIAL = not required / not activated`
|
||||||
|
|
||||||
@@ -39,6 +44,24 @@ Audit date: 2026-08-17. Active implementation: DEV-000067 revision 1.
|
|||||||
|
|
||||||
- `DEV_000065_STATUS = COMPLETED`
|
- `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`
|
- `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`
|
- `ADR_STATUS = ADR-0014 accepted`
|
||||||
- `OWNERSHIP_STATUS = no new owner; AttentionItem belongs to Continuity; notification delivery remains Commit/Capability/receiver path`
|
- `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`
|
- `REMINDER_LIFECYCLE_STATUS = ARMED -> TRIGGERED -> REVALIDATING -> DELIVERED implemented for due scanner`
|
||||||
|
|||||||
Reference in New Issue
Block a user