@@ -112,7 +112,10 @@ func (e *Engine) refreshGoalResearchProgress(goal *core.Goal, evaluation float64
|
||||
if n, err := strconv.Atoi(m[1]); err == nil && n > 0 {
|
||||
current, label := goal.ResearchEvidence, "quellengebundene Evidenzen"
|
||||
if strings.Contains(target, "artikel") || strings.Contains(target, "article") || strings.Contains(target, "draft") || strings.Contains(target, "entwurf") {
|
||||
current, label = goal.StagingDraftsCreated, "Staging-Artikel"
|
||||
current, label = 0, "validierte Staging-Artikel"
|
||||
if goal.LastStagingDraftID != "" && goal.StagingDraftValidated && goal.StagingQualityGateVersion == stagingQualityGateVersion {
|
||||
current = 1
|
||||
}
|
||||
} else {
|
||||
evidenceTarget := strings.Contains(target, "wissensein") || strings.Contains(target, "evidenz") || strings.Contains(target, "claim") || strings.Contains(target, "eintr")
|
||||
if !evidenceTarget && (strings.Contains(target, "quelle") || strings.Contains(target, "source")) {
|
||||
|
||||
@@ -286,17 +286,17 @@ func TestResearchMaterialRelevanceRejectsOffTopicWebRTCForNVIDIA(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoalArticleTargetUsesCreatedStagingArticles(t *testing.T) {
|
||||
func TestGoalArticleTargetUsesCurrentValidatedStagingDraft(t *testing.T) {
|
||||
s, err := store.New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
g := &core.Goal{ID: "goal-articles", Title: "NVIDIA", Target: "20 hochwertige Wissensartikel", StagingDraftsCreated: 1}
|
||||
g := &core.Goal{ID: "goal-articles", Title: "NVIDIA", Target: "20 hochwertige Wissensartikel", StagingDraftsCreated: 1, LastStagingDraftID: "KB-1", StagingDraftValidated: true, StagingQualityGateVersion: stagingQualityGateVersion}
|
||||
e := &Engine{store: s}
|
||||
e.refreshGoalResearchProgress(g, 0)
|
||||
if g.Progress < .049 || g.Progress > .051 || !strings.Contains(g.ProgressReason, "1/20 Staging-Artikel") {
|
||||
t.Fatalf("article target must count articles, got progress=%f reason=%q", g.Progress, g.ProgressReason)
|
||||
if g.Progress < .049 || g.Progress > .051 || !strings.Contains(g.ProgressReason, "1/20 validierte Staging-Artikel") {
|
||||
t.Fatalf("article target must count only a current validated draft, got progress=%f reason=%q", g.Progress, g.ProgressReason)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -630,3 +630,92 @@ func TestAuthoritativeDomainConfigurationRejectsOverbroadValues(t *testing.T) {
|
||||
t.Fatalf("unsafe authority domains were not sanitized: %#v", cfg.AuthoritativeDomains)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStagingRetriesOldQualityFailureOnceAfterGateUpgrade(t *testing.T) {
|
||||
requests := 0
|
||||
kb := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requests++
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"staging": map[string]any{"key": "KB-AI-STAGING-OLD", "meta": map[string]any{"integration_action": "updated"}}})
|
||||
}))
|
||||
defer kb.Close()
|
||||
|
||||
s, err := store.New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
src := &core.KnowledgeSource{ID: "src-1", Type: "web", Title: "Fortinet 7200", URI: "https://community.fortinet.com/fortigate/7200", Trust: .9, Status: "ready"}
|
||||
if err := s.UpsertSource(src); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
mem := &core.Memory{ID: "mem-1", Kind: "evidence", MemoryType: core.MemorySemantic, Text: "FortiClient SSLVPN error 7200 troubleshooting evidence.", Confidence: .8, Status: core.MemoryActive, Provenance: core.MemoryProvenance{Source: "web.page", GoalID: "goal-1", SourceID: src.ID}}
|
||||
if err := s.AddMemory(mem); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
run, err := s.StartResearchRun("goal-1", "FortiClient SSLVPN 7200")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, _ = s.FinishResearchRun(run.ID, "completed", "")
|
||||
|
||||
e := &Engine{store: s, http: kb.Client()}
|
||||
e.ConfigureStagingPublisher(StagingPublisherConfig{Enabled: true, URL: kb.URL, Token: "secret", MinEvidence: 1, MinSources: 1, SynthesisMode: "evidence"})
|
||||
g := &core.Goal{
|
||||
ID: "goal-1", Title: "FortiClient SSLVPN 7200", ResearchEvidence: 1, ResearchSources: 1,
|
||||
LastStagingDraftID: "KB-AI-STAGING-OLD",
|
||||
LastStagingError: "staging synthesis introduced source-unverified identifiers: /portal-konfiguration",
|
||||
}
|
||||
|
||||
e.maybePublishGoalDraft(context.Background(), g, ResearchResult{RunID: run.ID})
|
||||
if requests != 1 || g.LastStagingError != "" || !g.StagingDraftValidated || g.StagingQualityGateVersion != stagingQualityGateVersion {
|
||||
t.Fatalf("old quality failure was not revalidated: requests=%d goal=%#v", requests, g)
|
||||
}
|
||||
|
||||
// The same unchanged, already validated evidence must not be synthesized again.
|
||||
e.maybePublishGoalDraft(context.Background(), g, ResearchResult{RunID: run.ID})
|
||||
if requests != 1 {
|
||||
t.Fatalf("validated unchanged draft was republished: requests=%d", requests)
|
||||
}
|
||||
}
|
||||
|
||||
func TestArticleProgressRequiresCurrentValidatedDraft(t *testing.T) {
|
||||
s, err := store.New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
e := &Engine{store: s}
|
||||
g := &core.Goal{ID: "g1", Title: "Test", Target: "1 hochwertiger Wissensartikel", LastStagingDraftID: "KB-OLD", StagingDraftsCreated: 1}
|
||||
|
||||
e.refreshGoalResearchProgress(g, 0)
|
||||
if g.Progress != 0 || !strings.Contains(g.ProgressReason, "0/1 validierte Staging-Artikel") {
|
||||
t.Fatalf("unvalidated legacy draft must not satisfy article target: %#v", g)
|
||||
}
|
||||
|
||||
g.StagingDraftValidated = true
|
||||
g.StagingQualityGateVersion = stagingQualityGateVersion
|
||||
e.refreshGoalResearchProgress(g, 0)
|
||||
if g.Progress != 1 || !strings.Contains(g.ProgressReason, "1/1 validierte Staging-Artikel") {
|
||||
t.Fatalf("current validated draft should satisfy target: %#v", g)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStagingBelowThresholdInvalidatesLegacyDraftAndReplacesStaleError(t *testing.T) {
|
||||
s, err := store.New(t.TempDir())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer s.Close()
|
||||
e := &Engine{store: s}
|
||||
e.ConfigureStagingPublisher(StagingPublisherConfig{Enabled: true, URL: "http://knowledge.invalid", Token: "secret", MinEvidence: 4, MinSources: 2})
|
||||
g := &core.Goal{
|
||||
ID: "g1", LastStagingDraftID: "KB-OLD", StagingDraftValidated: true,
|
||||
StagingQualityGateVersion: "staging-v2",
|
||||
LastStagingError: "staging synthesis introduced source-unverified identifiers: /portal-konfiguration",
|
||||
}
|
||||
e.maybePublishGoalDraft(context.Background(), g, ResearchResult{})
|
||||
if g.StagingDraftValidated || g.StagingQualityGateVersion != stagingQualityGateVersion || !strings.Contains(g.LastStagingError, "quality gate not satisfied") {
|
||||
t.Fatalf("legacy draft state was not invalidated/reconciled: %#v", g)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package brain
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -14,6 +15,8 @@ import (
|
||||
"neuroforge/internal/core"
|
||||
)
|
||||
|
||||
const stagingQualityGateVersion = "staging-v3"
|
||||
|
||||
// StagingPublisherConfig configures the one-way governance bridge from
|
||||
// autonomous research into the human-review knowledge staging area.
|
||||
type StagingPublisherConfig struct {
|
||||
@@ -120,25 +123,25 @@ func (e *Engine) maybePublishGoalDraft(ctx context.Context, goal *core.Goal, res
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(cfg.URL) == "" || strings.TrimSpace(cfg.Token) == "" {
|
||||
goal.StagingDraftValidated = false
|
||||
goal.StagingQualityGateVersion = stagingQualityGateVersion
|
||||
goal.LastStagingError = "staging publisher enabled but URL/token is missing"
|
||||
_ = e.store.AddKnowledgeEvent(core.KnowledgeEvent{Type: "staging.publish_error", Summary: "Research draft could not be published", Reason: goal.LastStagingError, Actor: "goal-learning", Metadata: map[string]string{"goal_id": goal.ID}})
|
||||
return
|
||||
}
|
||||
if goal.LastStagingDraftID != "" && research.RunID != "" {
|
||||
if run, ok := e.store.GetResearchRun(research.RunID); ok && run.Stats.NewEvidence == 0 && run.Stats.Corroborations == 0 {
|
||||
// Avoid rewriting the same active staging draft every scheduler tick when
|
||||
// this cycle contributed no new information.
|
||||
return
|
||||
}
|
||||
}
|
||||
if goal.ResearchEvidence < cfg.MinEvidence || goal.ResearchSources < cfg.MinSources || goal.ResearchCorroborations < cfg.MinCorroborations {
|
||||
goal.LastStagingError = ""
|
||||
_ = e.store.AddKnowledgeEvent(core.KnowledgeEvent{Type: "staging.not_ready", Summary: "Research has not reached staging quality gate", Reason: fmt.Sprintf("evidence=%d/%d sources=%d/%d corroborations=%d/%d", goal.ResearchEvidence, cfg.MinEvidence, goal.ResearchSources, cfg.MinSources, goal.ResearchCorroborations, cfg.MinCorroborations), Actor: "goal-learning", Metadata: map[string]string{"goal_id": goal.ID}})
|
||||
goal.StagingDraftValidated = false
|
||||
goal.StagingQualityGateVersion = stagingQualityGateVersion
|
||||
goal.LastStagingAttemptSignature = ""
|
||||
goal.LastStagingError = fmt.Sprintf("staging quality gate not satisfied: evidence=%d/%d sources=%d/%d corroborations=%d/%d", goal.ResearchEvidence, cfg.MinEvidence, goal.ResearchSources, cfg.MinSources, goal.ResearchCorroborations, cfg.MinCorroborations)
|
||||
_ = e.store.AddKnowledgeEvent(core.KnowledgeEvent{Type: "staging.not_ready", Summary: "Research has not reached staging quality gate", Reason: goal.LastStagingError, Actor: "goal-learning", Metadata: map[string]string{"goal_id": goal.ID}})
|
||||
return
|
||||
}
|
||||
|
||||
evidence := e.collectGoalDraftEvidence(goal, cfg)
|
||||
if len(evidence) == 0 {
|
||||
goal.StagingDraftValidated = false
|
||||
goal.StagingQualityGateVersion = stagingQualityGateVersion
|
||||
goal.LastStagingError = "no active, goal-relevant source-backed evidence available for staging"
|
||||
return
|
||||
}
|
||||
@@ -158,15 +161,39 @@ func (e *Engine) maybePublishGoalDraft(ctx context.Context, goal *core.Goal, res
|
||||
}
|
||||
}
|
||||
if len(selectedSources) < cfg.MinSources {
|
||||
goal.StagingDraftValidated = false
|
||||
goal.StagingQualityGateVersion = stagingQualityGateVersion
|
||||
goal.LastStagingError = fmt.Sprintf("staging evidence diversity below threshold: relevant_sources=%d/%d", len(selectedSources), cfg.MinSources)
|
||||
return
|
||||
}
|
||||
authoritativeSources, independentOrigins, sourceAudit := summarizeEvidenceAuthority(cfg, evidence)
|
||||
if cfg.RequireAuthoritativeSource && authoritativeSources < cfg.MinAuthoritativeSources {
|
||||
goal.StagingDraftValidated = false
|
||||
goal.StagingQualityGateVersion = stagingQualityGateVersion
|
||||
goal.LastStagingError = fmt.Sprintf("staging source authority below threshold: authoritative_sources=%d/%d", authoritativeSources, cfg.MinAuthoritativeSources)
|
||||
_ = e.store.AddKnowledgeEvent(core.KnowledgeEvent{Type: "staging.not_ready", Summary: "Research draft lacks authoritative sources", Reason: goal.LastStagingError, Actor: "goal-learning", Metadata: map[string]string{"goal_id": goal.ID}})
|
||||
return
|
||||
}
|
||||
|
||||
attemptSignature := stagingEvidenceSignature(evidence)
|
||||
noNewInformation := false
|
||||
if research.RunID != "" {
|
||||
if run, ok := e.store.GetResearchRun(research.RunID); ok {
|
||||
noNewInformation = run.Stats.NewEvidence == 0 && run.Stats.Corroborations == 0
|
||||
}
|
||||
}
|
||||
if goal.LastStagingDraftID != "" && noNewInformation {
|
||||
if goal.StagingDraftValidated && goal.StagingQualityGateVersion == stagingQualityGateVersion && strings.TrimSpace(goal.LastStagingError) == "" {
|
||||
return
|
||||
}
|
||||
if goal.StagingQualityGateVersion == stagingQualityGateVersion && goal.LastStagingAttemptSignature == attemptSignature && deterministicStagingFailure(goal.LastStagingError) {
|
||||
return
|
||||
}
|
||||
}
|
||||
goal.StagingDraftValidated = false
|
||||
goal.StagingQualityGateVersion = stagingQualityGateVersion
|
||||
goal.LastStagingAttemptSignature = attemptSignature
|
||||
|
||||
draft, err := e.synthesizeGoalDraft(ctx, goal, evidence)
|
||||
if err != nil {
|
||||
goal.LastStagingError = err.Error()
|
||||
@@ -205,7 +232,7 @@ func (e *Engine) maybePublishGoalDraft(ctx context.Context, goal *core.Goal, res
|
||||
"research_evidence_ids": evidenceIDs,
|
||||
"research_source_uris": sourceURIs,
|
||||
"source_authority": sourceAudit,
|
||||
"quality_gate_version": "staging-v2",
|
||||
"quality_gate_version": stagingQualityGateVersion,
|
||||
"human_review_required": true,
|
||||
}
|
||||
if draft.Quality != nil && draft.Quality.Verification != nil {
|
||||
@@ -245,9 +272,49 @@ func (e *Engine) maybePublishGoalDraft(ctx context.Context, goal *core.Goal, res
|
||||
}
|
||||
goal.LastStagingDraftID = out.Staging.Key
|
||||
goal.LastStagingError = ""
|
||||
goal.StagingDraftValidated = true
|
||||
goal.StagingQualityGateVersion = stagingQualityGateVersion
|
||||
goal.LastStagingAttemptSignature = attemptSignature
|
||||
_ = e.store.AddKnowledgeEvent(core.KnowledgeEvent{Type: "staging.draft_" + firstNonEmpty(action, "created"), Summary: "Research proposal sent to human-review staging", Reason: "research quality gate satisfied", Actor: "goal-learning", Metadata: map[string]string{"goal_id": goal.ID, "staging_id": out.Staging.Key, "action": action}})
|
||||
}
|
||||
|
||||
func stagingEvidenceSignature(evidence []draftEvidence) string {
|
||||
parts := make([]string, 0, len(evidence)*2+1)
|
||||
parts = append(parts, stagingQualityGateVersion)
|
||||
for _, ev := range evidence {
|
||||
parts = append(parts, ev.Memory.ID, ev.Memory.Provenance.SourceID)
|
||||
for _, src := range ev.CorroboratingSources {
|
||||
if src != nil {
|
||||
parts = append(parts, src.ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Strings(parts[1:])
|
||||
sum := sha256.Sum256([]byte(strings.Join(parts, "\x00")))
|
||||
return fmt.Sprintf("%s:%x", stagingQualityGateVersion, sum[:12])
|
||||
}
|
||||
|
||||
func deterministicStagingFailure(message string) bool {
|
||||
m := strings.ToLower(strings.TrimSpace(message))
|
||||
if m == "" {
|
||||
return false
|
||||
}
|
||||
for _, prefix := range []string{
|
||||
"staging synthesis ",
|
||||
"invalid staging synthesis ",
|
||||
"claim verification ",
|
||||
"staging source authority ",
|
||||
"staging evidence diversity ",
|
||||
"no active, goal-relevant ",
|
||||
"staging quality gate ",
|
||||
} {
|
||||
if strings.HasPrefix(m, prefix) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return strings.Contains(m, "source-unverified identifiers")
|
||||
}
|
||||
|
||||
func (e *Engine) collectGoalDraftEvidence(goal *core.Goal, cfg StagingPublisherConfig) []draftEvidence {
|
||||
if goal == nil {
|
||||
return nil
|
||||
@@ -412,7 +479,7 @@ func (e *Engine) synthesizeGoalDraft(ctx context.Context, goal *core.Goal, evide
|
||||
Text: "Automatisch recherchiertes Evidence-Bundle. Keine Artikelsynthese; menschliche Prüfung ist zwingend erforderlich.\n\n" + evidencePack,
|
||||
Answer: answer, Categories: []string{"Research", goal.Title}, Keywords: goalKeywords(goal), MinScore: .85,
|
||||
IntegrationKey: "neuroforge-goal:" + goal.ID,
|
||||
Quality: &stagingQualityMetadata{GateVersion: "staging-v2", AuthoritativeSources: auth, IndependentOrigins: origins, SourceAudit: audit},
|
||||
Quality: &stagingQualityMetadata{GateVersion: stagingQualityGateVersion, AuthoritativeSources: auth, IndependentOrigins: origins, SourceAudit: audit},
|
||||
}, nil
|
||||
}
|
||||
if cfg.SynthesisMode != "llm" {
|
||||
@@ -473,7 +540,7 @@ func (e *Engine) synthesizeGoalDraft(ctx context.Context, goal *core.Goal, evide
|
||||
return stagingDraftPayload{}, err
|
||||
}
|
||||
auth, origins, audit := summarizeEvidenceAuthority(cfg, evidence)
|
||||
draft.Quality = &stagingQualityMetadata{GateVersion: "staging-v2", AuthoritativeSources: auth, IndependentOrigins: origins, SourceAudit: audit}
|
||||
draft.Quality = &stagingQualityMetadata{GateVersion: stagingQualityGateVersion, AuthoritativeSources: auth, IndependentOrigins: origins, SourceAudit: audit}
|
||||
if !cfg.VerifyClaims {
|
||||
return draft, nil
|
||||
}
|
||||
@@ -485,7 +552,7 @@ func (e *Engine) synthesizeGoalDraft(ctx context.Context, goal *core.Goal, evide
|
||||
repairedReport, secondErr := e.verifyDraftClaims(ctx, goal, evidence, repairedDraft)
|
||||
if secondErr == nil {
|
||||
repairedReport.RepairApplied = true
|
||||
repairedDraft.Quality = &stagingQualityMetadata{GateVersion: "staging-v2", AuthoritativeSources: auth, IndependentOrigins: origins, SourceAudit: audit, Verification: &repairedReport}
|
||||
repairedDraft.Quality = &stagingQualityMetadata{GateVersion: stagingQualityGateVersion, AuthoritativeSources: auth, IndependentOrigins: origins, SourceAudit: audit, Verification: &repairedReport}
|
||||
return repairedDraft, nil
|
||||
}
|
||||
verifyErr = fmt.Errorf("%v; grounded repair verification failed: %w", verifyErr, secondErr)
|
||||
|
||||
@@ -523,36 +523,39 @@ type MaintenanceStatus struct {
|
||||
}
|
||||
|
||||
type Goal struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
Priority int `json:"priority"`
|
||||
Progress float64 `json:"progress"`
|
||||
Target string `json:"target,omitempty"`
|
||||
Prediction string `json:"prediction,omitempty"`
|
||||
NextAction string `json:"next_action,omitempty"`
|
||||
LastEvaluation float64 `json:"last_evaluation,omitempty"`
|
||||
ProgressReason string `json:"progress_reason,omitempty"`
|
||||
ResearchEvidence int `json:"research_evidence,omitempty"`
|
||||
ResearchSources int `json:"research_sources,omitempty"`
|
||||
ResearchCorroborations int `json:"research_corroborations,omitempty"`
|
||||
ResearchSourceIDs []string `json:"research_source_ids,omitempty"`
|
||||
StagingDraftsCreated int `json:"staging_drafts_created,omitempty"`
|
||||
StagingDraftsUpdated int `json:"staging_drafts_updated,omitempty"`
|
||||
LastStagingDraftID string `json:"last_staging_draft_id,omitempty"`
|
||||
LastStagingError string `json:"last_staging_error,omitempty"`
|
||||
MemoryIDs []string `json:"memory_ids,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
LastCycleAt time.Time `json:"last_cycle_at,omitempty"`
|
||||
AutoRun bool `json:"auto_run"`
|
||||
IntervalMinutes int `json:"interval_minutes,omitempty"`
|
||||
NextCycleAt time.Time `json:"next_cycle_at,omitempty"`
|
||||
ResearchEnabled bool `json:"research_enabled"`
|
||||
ConsecutiveErrors int `json:"consecutive_errors,omitempty"`
|
||||
LastError string `json:"last_error,omitempty"`
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"`
|
||||
Description string `json:"description"`
|
||||
Status string `json:"status"`
|
||||
Priority int `json:"priority"`
|
||||
Progress float64 `json:"progress"`
|
||||
Target string `json:"target,omitempty"`
|
||||
Prediction string `json:"prediction,omitempty"`
|
||||
NextAction string `json:"next_action,omitempty"`
|
||||
LastEvaluation float64 `json:"last_evaluation,omitempty"`
|
||||
ProgressReason string `json:"progress_reason,omitempty"`
|
||||
ResearchEvidence int `json:"research_evidence,omitempty"`
|
||||
ResearchSources int `json:"research_sources,omitempty"`
|
||||
ResearchCorroborations int `json:"research_corroborations,omitempty"`
|
||||
ResearchSourceIDs []string `json:"research_source_ids,omitempty"`
|
||||
StagingDraftsCreated int `json:"staging_drafts_created,omitempty"`
|
||||
StagingDraftsUpdated int `json:"staging_drafts_updated,omitempty"`
|
||||
LastStagingDraftID string `json:"last_staging_draft_id,omitempty"`
|
||||
LastStagingError string `json:"last_staging_error,omitempty"`
|
||||
StagingDraftValidated bool `json:"staging_draft_validated,omitempty"`
|
||||
StagingQualityGateVersion string `json:"staging_quality_gate_version,omitempty"`
|
||||
LastStagingAttemptSignature string `json:"last_staging_attempt_signature,omitempty"`
|
||||
MemoryIDs []string `json:"memory_ids,omitempty"`
|
||||
Tags []string `json:"tags,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
LastCycleAt time.Time `json:"last_cycle_at,omitempty"`
|
||||
AutoRun bool `json:"auto_run"`
|
||||
IntervalMinutes int `json:"interval_minutes,omitempty"`
|
||||
NextCycleAt time.Time `json:"next_cycle_at,omitempty"`
|
||||
ResearchEnabled bool `json:"research_enabled"`
|
||||
ConsecutiveErrors int `json:"consecutive_errors,omitempty"`
|
||||
LastError string `json:"last_error,omitempty"`
|
||||
}
|
||||
|
||||
type LearningCycle struct {
|
||||
|
||||
@@ -456,6 +456,12 @@ components:
|
||||
type: string
|
||||
last_staging_error:
|
||||
type: string
|
||||
staging_draft_validated:
|
||||
type: boolean
|
||||
staging_quality_gate_version:
|
||||
type: string
|
||||
last_staging_attempt_signature:
|
||||
type: string
|
||||
memory_ids:
|
||||
type: array
|
||||
items:
|
||||
|
||||
Reference in New Issue
Block a user