merge: GEN2-EVOLVE-002 iterative retrieval
CI / test (push) Failing after 35s

This commit is contained in:
m
2026-08-30 20:47:05 +03:00
11 changed files with 353 additions and 51 deletions
+45 -1
View File
@@ -135,7 +135,51 @@ func (g cognitiveGateway) advisoryHandler(w http.ResponseWriter, r *http.Request
writeGatewayResponse(w, g.invokeBuiltinAdvisory(r.Context(), request, maxOutputBytes))
return
}
writeGatewayResponse(w, cognitive.GatewayAdvisoryResponse{Version: response.Version, RequestID: response.RequestID, AdvisoryText: response.AdvisoryText, ReferencedExcerptIDs: response.ReferencedExcerptIDs, FinishReason: response.FinishReason, Usage: response.Usage})
writeGatewayResponse(w, gatewayAdvisoryResponse(request, response))
}
const iterativeRetrievalMarker = "<domovoy-retrieval>"
// gatewayAdvisoryResponse is the only model-facing extraction point. The
// model may express a bounded retrieval intent, but it cannot provide scope,
// identity, policy or storage details; Core supplies those on continuation.
func gatewayAdvisoryResponse(request cognitive.GatewayAdvisoryRequest, response cognitive.AdvisoryResponse) cognitive.GatewayAdvisoryResponse {
result := cognitive.GatewayAdvisoryResponse{Version: response.Version, RequestID: response.RequestID, AdvisoryText: response.AdvisoryText, ReferencedExcerptIDs: response.ReferencedExcerptIDs, FinishReason: response.FinishReason, Usage: response.Usage}
if !request.AllowIterativeRetrieval || response.FinishReason != cognitive.FinishCompleted {
return result
}
intent, text, ok := parseIterativeRetrievalIntent(response.AdvisoryText)
if !ok {
return result
}
result.AdvisoryText = text
result.IterativeRetrieval = &intent
return result
}
func parseIterativeRetrievalIntent(text string) (cognitive.IterativeRetrievalRequest, string, bool) {
start := strings.LastIndex(text, iterativeRetrievalMarker)
if start < 0 {
return cognitive.IterativeRetrievalRequest{}, text, false
}
payload := strings.TrimSpace(text[start+len(iterativeRetrievalMarker):])
var intent cognitive.IterativeRetrievalRequest
if err := json.Unmarshal([]byte(payload), &intent); err != nil || !validIterativeRetrievalIntent(intent) {
return cognitive.IterativeRetrievalRequest{}, text, false
}
return intent, strings.TrimSpace(text[:start]), true
}
func validIterativeRetrievalIntent(intent cognitive.IterativeRetrievalRequest) bool {
if strings.TrimSpace(intent.Query) == "" || strings.TrimSpace(intent.Reason) == "" || len([]rune(intent.Query)) > 512 || len([]rune(intent.Reason)) > 512 {
return false
}
switch intent.Lane {
case cognitive.IterativeRetrievalCurrentFact, cognitive.IterativeRetrievalPositiveExperience, cognitive.IterativeRetrievalNegativeExperience, cognitive.IterativeRetrievalContradiction, cognitive.IterativeRetrievalHistoricalAnalogy:
return true
default:
return false
}
}
func (g cognitiveGateway) invokeAdmitted(ctx context.Context, leaseID string, provider advisoryProvider, invocation cognitive.AdvisoryRequest, maxOutputBytes int) (cognitive.AdvisoryResponse, error) {
@@ -108,6 +108,20 @@ func TestActiveRegistryBindingReachesProviderAdapter(t *testing.T) {
}
}
func TestGatewayExtractsOnlyBoundedTypedRetrievalIntent(t *testing.T) {
request := cognitive.GatewayAdvisoryRequest{AllowIterativeRetrieval: true}
response := cognitive.AdvisoryResponse{Version: cognitive.CreatorAdvisoryV1, RequestID: "request", FinishReason: cognitive.FinishCompleted, AdvisoryText: "<domovoy-retrieval>{\"Query\":\"sync catalog\",\"SemanticSubject\":\"catalog sync\",\"Lane\":\"NEGATIVE_EXPERIENCE\",\"Reason\":\"need known failure\"}"}
got := gatewayAdvisoryResponse(request, response)
if got.IterativeRetrieval == nil || got.IterativeRetrieval.Lane != cognitive.IterativeRetrievalNegativeExperience || got.AdvisoryText != "" {
t.Fatalf("response=%+v", got)
}
invalid := response
invalid.AdvisoryText = "<domovoy-retrieval>{\"Query\":\"all memory\",\"Lane\":\"UNRESTRICTED\",\"Reason\":\"everything\"}"
if got := gatewayAdvisoryResponse(request, invalid); got.IterativeRetrieval != nil {
t.Fatalf("invalid intent accepted: %+v", got)
}
}
func TestStaleUnavailableBindingRevalidatesOnceBeforeInvocation(t *testing.T) {
selected := gatewaySelection()
selected.Binding.DataPolicy = capability.DataPolicyLocalOnly
+135 -30
View File
@@ -42,6 +42,19 @@ func compileInteractionContextWithExperience(projection interactiondomain.Contex
if len(projection.Fragments) == 0 && len(experiences) == 0 {
return projection.Text, nil
}
candidates := interactionContextCandidates(projection, focusProjectID, experiences, query)
bundle, err := cognitive.CompileContext(cognitive.ContextCompileInput{Candidates: candidates, DataPolicy: capability.DataPolicyLocalOnly, MaxBytes: 2048, ContextScopeRef: focusProjectID, FreshnessRequirement: "conversation-current", RiskClass: "low"})
if err != nil {
return "", err
}
parts := make([]string, 0, len(bundle.Excerpts))
for _, excerpt := range bundle.Excerpts {
parts = append(parts, excerpt.Text)
}
return strings.Join(parts, "\n"), nil
}
func interactionContextCandidates(projection interactiondomain.ContextProjection, focusProjectID string, experiences []epistemics.ExperienceRecord, query string) []cognitive.ContextCandidate {
candidates := make([]cognitive.ContextCandidate, 0, len(projection.Fragments)+len(experiences))
for _, fragment := range projection.Fragments {
candidates = append(candidates, cognitive.ContextCandidate{
@@ -70,15 +83,7 @@ func compileInteractionContextWithExperience(projection interactiondomain.Contex
for _, record := range retrieved.Contradictions {
candidates = append(candidates, experienceContextCandidate(record, "contradiction", true))
}
bundle, err := cognitive.CompileContext(cognitive.ContextCompileInput{Candidates: candidates, DataPolicy: capability.DataPolicyLocalOnly, MaxBytes: 2048, ContextScopeRef: focusProjectID, FreshnessRequirement: "conversation-current", RiskClass: "low"})
if err != nil {
return "", err
}
parts := make([]string, 0, len(bundle.Excerpts))
for _, excerpt := range bundle.Excerpts {
parts = append(parts, excerpt.Text)
}
return strings.Join(parts, "\n"), nil
return candidates
}
func needsHistoricalAnalogy(query string) bool {
@@ -101,6 +106,69 @@ func experienceContextCandidate(record epistemics.ExperienceRecord, lane string,
return cognitive.ContextCandidate{Excerpt: cognitive.ContextExcerpt{ID: cognitive.ContextExcerptID("experience:" + string(record.ID)), SourceKind: "epistemics-experience", SourceID: "epistemics:experience-records", Revision: 1, Sensitivity: cognitive.SensitivityPrivateAllowed, Text: text}, Relevance: 1, Lane: lane, ContextScopeRef: record.ContextScopeRef, FreshnessState: "CURRENT", Negative: negative}
}
const (
iterativeRetrievalMaxRounds = 1
iterativeRetrievalMaxFragments = 2
iterativeRetrievalMaxAddedBytes = 1024
iterativeRetrievalMaxLatency = 1500 * time.Millisecond
)
func newIterativeContextRetriever(history []interaction.HumanUtterance, references []interaction.ProjectReference, principalID, focusProjectID string, experiences []epistemics.ExperienceRecord) iterativeContextRetriever {
seen := map[string]struct{}{}
return func(ctx context.Context, request cognitive.IterativeRetrievalRequest) cognitive.IterativeRetrievalResponse {
started := time.Now()
if request.Pass < 0 || request.Pass >= iterativeRetrievalMaxRounds || request.RemainingBytes <= 0 || request.RemainingBytes > iterativeRetrievalMaxAddedBytes {
return cognitive.IterativeRetrievalResponse{Exhausted: true, Reason: "budget"}
}
projection := interactiondomain.BuildScopedGen2ContextForQuery(history, references, principalID, focusProjectID, request.Query)
candidates := interactionContextCandidates(projection, focusProjectID, experiences, request.Query)
filtered := make([]cognitive.ContextCandidate, 0, len(candidates))
for _, candidate := range candidates {
if !iterativeLaneMatches(request.Lane, candidate.Lane) {
continue
}
key := string(request.Lane) + ":" + string(candidate.Excerpt.ID)
if _, duplicate := seen[key]; duplicate {
continue
}
filtered = append(filtered, candidate)
}
bundle, err := cognitive.CompileContext(cognitive.ContextCompileInput{Candidates: filtered, DataPolicy: capability.DataPolicyLocalOnly, MaxBytes: request.RemainingBytes, ContextScopeRef: focusProjectID, TaskRef: "chat", FocusFrameRef: focusProjectID, FreshnessRequirement: "conversation-current"})
if err != nil || len(bundle.Excerpts) == 0 {
return cognitive.IterativeRetrievalResponse{Exhausted: true, Reason: "no_additional_evidence"}
}
out := make([]cognitive.ContextCandidate, 0, iterativeRetrievalMaxFragments)
for _, excerpt := range bundle.Excerpts {
if len(out) == iterativeRetrievalMaxFragments || time.Since(started) > iterativeRetrievalMaxLatency {
break
}
seen[string(request.Lane)+":"+string(excerpt.ID)] = struct{}{}
for _, candidate := range filtered {
if candidate.Excerpt.ID == excerpt.ID {
out = append(out, candidate)
break
}
}
}
if len(out) == 0 {
return cognitive.IterativeRetrievalResponse{Exhausted: true, Reason: "latency_budget"}
}
addedBytes := 0
for _, candidate := range out {
addedBytes += len([]byte(candidate.Excerpt.Text))
}
return cognitive.IterativeRetrievalResponse{Candidates: out, Exhausted: true, Reason: "completed", AddedBytes: addedBytes}
}
}
func iterativeLaneMatches(request cognitive.IterativeRetrievalLane, actual string) bool {
return (request == cognitive.IterativeRetrievalCurrentFact && actual == "current-fact") ||
(request == cognitive.IterativeRetrievalPositiveExperience && actual == "positive-experience") ||
(request == cognitive.IterativeRetrievalNegativeExperience && actual == "negative-experience") ||
(request == cognitive.IterativeRetrievalContradiction && actual == "contradiction") ||
(request == cognitive.IterativeRetrievalHistoricalAnalogy && actual == "historical-analogy")
}
const (
// Household interpretation and response review are intentionally compact
// calls. Keeping their output bounded prevents a loaded local model from
@@ -168,43 +236,53 @@ func (c cognitiveInterpretationClient) ReviewResponse(ctx context.Context, sessi
// non-current question only after evidence retrieval did not produce a usable
// result. The model decides whether its knowledge is stable enough; it must
// explicitly decline changing or source-dependent facts instead of guessing.
type iterativeContextRetriever func(context.Context, cognitive.IterativeRetrievalRequest) cognitive.IterativeRetrievalResponse
func (c cognitiveInterpretationClient) AnswerStableKnowledge(ctx context.Context, session identity.CreatorSession, userMessage, personalMemory string, meaning cognitive.TaskMeaning) (string, error) {
return c.AnswerStableKnowledgeWithContinuation(ctx, session, userMessage, personalMemory, meaning, nil)
}
// AnswerStableKnowledgeWithContinuation permits one model-requested, Core-run
// retrieval round. The callback receives only a typed intent and cannot alter
// identity, scope or provider selection.
func (c cognitiveInterpretationClient) AnswerStableKnowledgeWithContinuation(ctx context.Context, session identity.CreatorSession, userMessage, personalMemory string, meaning cognitive.TaskMeaning, retrieve iterativeContextRetriever) (string, error) {
requestID, err := ids.New()
if err != nil {
return "", err
}
prompt := "Ты отвечаешь как Домовой на обычное сообщение пользователя. Сначала пойми цель: на вопрос дай короткий естественный ответ по устойчивому общеизвестному знанию, а безопасную просьбу создать текстовый или структурный результат выполни прямо в ответе. Начинай сразу с содержания и не представляйся, если пользователь не спросил об имени. Не называй модель или внутреннюю систему. Если истинность ответа зависит от текущего наблюдения, внешнего источника или инструмента, верни ровно NEEDS_VERIFIED_RESEARCH. Не придумывай факты и не утверждай выполнение внешнего действия.\n\nСообщение пользователя:\n" + userMessage
if retrieve != nil {
prompt += "\n\nЕсли именно для ответа не хватает ограниченного контекста, вместо ответа верни единственную строку <domovoy-retrieval>{\"Query\":\"...\",\"SemanticSubject\":\"...\",\"Lane\":\"CURRENT_FACT|POSITIVE_EXPERIENCE|NEGATIVE_EXPERIENCE|CONTRADICTION|HISTORICAL_ANALOGY\",\"Reason\":\"...\"}. Не запрашивай широкий дамп памяти."
}
context := []cognitive.ContextExcerpt{}
if strings.TrimSpace(personalMemory) != "" {
context = append(context, cognitive.ContextExcerpt{ID: "personal-memory", SourceKind: "derived-personal-memory", SourceID: "interaction:human-utterances", Revision: 1, Sensitivity: cognitive.SensitivityPrivateAllowed, Text: personalMemory})
}
target := *c.baseURL
target.Path = "/v1/advisories"
payload, err := json.Marshal(cognitive.GatewayAdvisoryRequest{
requestValue := cognitive.GatewayAdvisoryRequest{
Version: cognitive.CreatorAdvisoryV1, RequestID: cognitive.RequestID(requestID),
ActorID: session.ActorID, PrincipalID: session.PrincipalID, ScopeID: authority.ScopeID("domovoy-global-scope"),
Message: prompt, Context: context, Locale: "ru", MaxOutputBytes: stableKnowledgeMaxOutputBytes, DataPolicy: capability.DataPolicyLocalOnly,
Meaning: cognitive.TaskMeaning{SpeechAct: "STABLE_KNOWLEDGE_ANSWER", CapabilityHints: meaning.CapabilityHints, RequestedOutcome: meaning.RequestedOutcome, InformationRequestKind: meaning.InformationRequestKind, ConfidenceMillis: meaning.ConfidenceMillis},
})
AllowIterativeRetrieval: retrieve != nil,
Meaning: cognitive.TaskMeaning{SpeechAct: "STABLE_KNOWLEDGE_ANSWER", CapabilityHints: meaning.CapabilityHints, RequestedOutcome: meaning.RequestedOutcome, InformationRequestKind: meaning.InformationRequestKind, ConfidenceMillis: meaning.ConfidenceMillis},
}
result, err := c.requestAdvisory(ctx, requestValue)
if err != nil {
return "", err
}
request, err := http.NewRequestWithContext(ctx, http.MethodPost, target.String(), bytes.NewReader(payload))
if err != nil {
return "", err
}
request.Header.Set("Content-Type", "application/json")
response, err := c.client.Do(request)
if err != nil {
return "", err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return "", fmt.Errorf("cognitive stable knowledge status %d", response.StatusCode)
}
var result cognitive.GatewayAdvisoryResponse
if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
return "", err
if result.IterativeRetrieval != nil && retrieve != nil {
additional := retrieve(ctx, *result.IterativeRetrieval)
for _, candidate := range additional.Candidates {
context = append(context, candidate.Excerpt)
}
if len(additional.Candidates) == 0 {
context = append(context, cognitive.ContextExcerpt{ID: "continuation:no-additional-evidence", SourceKind: "iterative-retrieval", SourceID: "core", Revision: 1, Sensitivity: cognitive.SensitivityPrivateAllowed, Text: "No additional evidence was found; answer with uncertainty rather than inventing facts."})
}
requestValue.Context = context
requestValue.AllowIterativeRetrieval = false
result, err = c.requestAdvisory(ctx, requestValue)
if err != nil {
return "", err
}
}
if result.FinishReason != cognitive.FinishCompleted || strings.TrimSpace(result.AdvisoryText) == "" {
return "", fmt.Errorf("cognitive stable knowledge incomplete: %s", result.FinishReason)
@@ -212,6 +290,33 @@ func (c cognitiveInterpretationClient) AnswerStableKnowledge(ctx context.Context
return strings.TrimSpace(result.AdvisoryText), nil
}
func (c cognitiveInterpretationClient) requestAdvisory(ctx context.Context, value cognitive.GatewayAdvisoryRequest) (cognitive.GatewayAdvisoryResponse, error) {
target := *c.baseURL
target.Path = "/v1/advisories"
payload, err := json.Marshal(value)
if err != nil {
return cognitive.GatewayAdvisoryResponse{}, err
}
request, err := http.NewRequestWithContext(ctx, http.MethodPost, target.String(), bytes.NewReader(payload))
if err != nil {
return cognitive.GatewayAdvisoryResponse{}, err
}
request.Header.Set("Content-Type", "application/json")
response, err := c.client.Do(request)
if err != nil {
return cognitive.GatewayAdvisoryResponse{}, err
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return cognitive.GatewayAdvisoryResponse{}, fmt.Errorf("cognitive stable knowledge status %d", response.StatusCode)
}
var result cognitive.GatewayAdvisoryResponse
if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
return cognitive.GatewayAdvisoryResponse{}, err
}
return result, nil
}
func newCognitiveInterpretationClient(rawURL string, client *http.Client) (cognitiveInterpretationClient, error) {
base, err := url.Parse(rawURL)
if err != nil || base.Scheme == "" || base.Host == "" {
@@ -11,6 +11,7 @@ import (
"git.cin.su/m/Assistant/contracts/capability"
"git.cin.su/m/Assistant/contracts/cognitive"
"git.cin.su/m/Assistant/contracts/epistemics"
"git.cin.su/m/Assistant/contracts/identity"
"git.cin.su/m/Assistant/contracts/interaction"
interactiondomain "git.cin.su/m/Assistant/modules/interaction/domain"
@@ -92,6 +93,62 @@ func TestCompileInteractionContextUsesHistoricalOnlyWhenRequested(t *testing.T)
}
}
func TestStableKnowledgeContinuesOnceWithBoundedAdditionalContext(t *testing.T) {
calls := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
var request cognitive.GatewayAdvisoryRequest
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
t.Fatal(err)
}
if calls == 1 {
if !request.AllowIterativeRetrieval {
t.Fatal("initial advisory did not permit bounded continuation")
}
_ = json.NewEncoder(w).Encode(cognitive.GatewayAdvisoryResponse{Version: request.Version, RequestID: request.RequestID, FinishReason: cognitive.FinishCompleted, IterativeRetrieval: &cognitive.IterativeRetrievalRequest{Query: "контрольный код", Lane: cognitive.IterativeRetrievalCurrentFact, Reason: "initial context is insufficient", RemainingBytes: 512}})
return
}
if request.AllowIterativeRetrieval || len(request.Context) != 1 || request.Context[0].Text != "контрольный код = ORBIT-731" {
t.Fatalf("continuation request=%+v", request)
}
_ = json.NewEncoder(w).Encode(cognitive.GatewayAdvisoryResponse{Version: request.Version, RequestID: request.RequestID, FinishReason: cognitive.FinishCompleted, AdvisoryText: "Контрольный код — ORBIT-731."})
}))
defer server.Close()
client, err := newCognitiveInterpretationClient(server.URL, server.Client())
if err != nil {
t.Fatal(err)
}
answer, err := client.AnswerStableKnowledgeWithContinuation(context.Background(), identity.CreatorSession{ActorID: "actor", PrincipalID: "principal"}, "Какой контрольный код?", "", cognitive.TaskMeaning{SpeechAct: "QUESTION"}, func(context.Context, cognitive.IterativeRetrievalRequest) cognitive.IterativeRetrievalResponse {
return cognitive.IterativeRetrievalResponse{Candidates: []cognitive.ContextCandidate{{Excerpt: cognitive.ContextExcerpt{ID: "interaction:code", Text: "контрольный код = ORBIT-731", Sensitivity: cognitive.SensitivityPrivateAllowed}, Lane: "current-fact"}}, Exhausted: true, AddedBytes: 28}
})
if err != nil || answer != "Контрольный код — ORBIT-731." || calls != 2 {
t.Fatalf("answer=%q err=%v calls=%d", answer, err, calls)
}
}
func TestIterativeRetrieverPreservesScopeLanesAndBudgets(t *testing.T) {
history := []interaction.HumanUtterance{{ID: "a", PrincipalID: "p", Content: "контрольный код = ALPHA", ReceivedAtUnixMillis: 1}, {ID: "b", PrincipalID: "p", Content: "контрольный код = BETA", ReceivedAtUnixMillis: 2}}
references := []interaction.ProjectReference{{ID: "a-ref", UtteranceID: "a", ProjectID: "company-a"}, {ID: "b-ref", UtteranceID: "b", ProjectID: "company-b"}}
experiences := []epistemics.ExperienceRecord{{ID: "failure", PrincipalID: "p", ContextScopeRef: "company-a", ProblemStatement: "catalog sync", ActualOutcome: epistemics.OutcomeFailed, OutcomeDetail: "old connector failed", RecordedAtUnixMillis: 1}, {ID: "conflict", PrincipalID: "p", ContextScopeRef: "company-a", ProblemStatement: "catalog sync", ActualOutcome: epistemics.OutcomeConflict, OutcomeDetail: "conflicting result", RecordedAtUnixMillis: 2}}
retrieve := newIterativeContextRetriever(history, references, "p", "company-a", experiences)
current := retrieve(context.Background(), cognitive.IterativeRetrievalRequest{Query: "контрольный код", Lane: cognitive.IterativeRetrievalCurrentFact, Reason: "need scoped fact", RemainingBytes: 512})
if len(current.Candidates) != 1 || !strings.Contains(current.Candidates[0].Excerpt.Text, "ALPHA") || strings.Contains(current.Candidates[0].Excerpt.Text, "BETA") {
t.Fatalf("scope leaked: %#v", current)
}
if duplicate := retrieve(context.Background(), cognitive.IterativeRetrievalRequest{Query: "контрольный код", Lane: cognitive.IterativeRetrievalCurrentFact, Reason: "repeat", RemainingBytes: 512}); duplicate.Reason != "no_additional_evidence" {
t.Fatalf("duplicate was re-added: %#v", duplicate)
}
if negative := retrieve(context.Background(), cognitive.IterativeRetrievalRequest{Query: "catalog sync", Lane: cognitive.IterativeRetrievalNegativeExperience, Reason: "need failure", RemainingBytes: 512}); len(negative.Candidates) < 1 || !strings.Contains(strings.Join([]string{negative.Candidates[0].Excerpt.Text, negative.Candidates[len(negative.Candidates)-1].Excerpt.Text}, " "), "old connector failed") {
t.Fatalf("negative lane=%#v", negative)
}
if contradiction := retrieve(context.Background(), cognitive.IterativeRetrievalRequest{Query: "catalog sync", Lane: cognitive.IterativeRetrievalContradiction, Reason: "need conflict", RemainingBytes: 512}); len(contradiction.Candidates) != 1 || !strings.Contains(contradiction.Candidates[0].Excerpt.Text, "conflicting result") {
t.Fatalf("contradiction lane=%#v", contradiction)
}
if budget := retrieve(context.Background(), cognitive.IterativeRetrievalRequest{Query: "anything", Lane: cognitive.IterativeRetrievalCurrentFact, Reason: "bad", RemainingBytes: 2048}); budget.Reason != "budget" {
t.Fatalf("budget=%#v", budget)
}
}
func TestGenerateImageUsesDedicatedCognitiveMediaRoute(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost || r.URL.Path != "/v1/multimodal/generate-image" {
+5 -4
View File
@@ -604,6 +604,7 @@ func main() {
} else {
gen2Context.Text = compiledContext
}
iterativeRetriever := newIterativeContextRetriever(conversationHistory, contextProjectReferences, string(session.PrincipalID), focusProjectID, experienceRecords)
if gen2Context.UsedPersonalMemoryFallback {
telemetry.RecordCompatibility("personal-memory-fallback", "core-runtime", "read", "gen2-context-empty", true, true, false, "USED", utterance.CorrelationID, string(session.PrincipalID), now)
}
@@ -931,9 +932,9 @@ func main() {
reply, backed := renderResearchReply(searchQuery, result, researchErr)
if !backed && cognitiveAvailable {
answerCtx, cancelAnswer := context.WithTimeout(r.Context(), 12*time.Second)
answer, answerErr := cognitiveClient.AnswerStableKnowledge(answerCtx, session, part, gen2Context.Text, cognitive.TaskMeaning{
answer, answerErr := cognitiveClient.AnswerStableKnowledgeWithContinuation(answerCtx, session, part, gen2Context.Text, cognitive.TaskMeaning{
SpeechAct: string(semantic.Interpretation.SpeechAct), CapabilityHints: toCapabilityKinds(semantic.CapabilityHints), RequestedOutcome: semantic.Interpretation.RequestedOutcome, InformationRequestKind: semantic.InformationRequestKind, ConfidenceMillis: semantic.Interpretation.ConfidenceMillis,
})
}, iterativeRetriever)
cancelAnswer()
if answerErr == nil && stableKnowledgeAnswer(answer) {
reply, backed = answer, true
@@ -971,9 +972,9 @@ func main() {
canGenerateTextReply := shouldGenerateNonMutatingTextDirective(semantic, routePlan, helpPath, creativeRequest) && planningReply == deterministicReply
if (planningReply == "" && needsCognition && semantic.Interpretation.SpeechAct == interaction.SpeechActQuestion || canGenerateTextReply) && (!hasChatRoute(routePlan, chatRouteResearch) || len(gen2Context.Fragments) > 0) && !creativeRequest {
answerCtx, cancelAnswer := context.WithTimeout(r.Context(), 12*time.Second)
answer, answerErr := cognitiveClient.AnswerStableKnowledge(answerCtx, session, effectiveTaskMessage, gen2Context.Text, cognitive.TaskMeaning{
answer, answerErr := cognitiveClient.AnswerStableKnowledgeWithContinuation(answerCtx, session, effectiveTaskMessage, gen2Context.Text, cognitive.TaskMeaning{
SpeechAct: string(semantic.Interpretation.SpeechAct), CapabilityHints: toCapabilityKinds(semantic.CapabilityHints), RequestedOutcome: semantic.Interpretation.RequestedOutcome, InformationRequestKind: semantic.InformationRequestKind, ConfidenceMillis: semantic.Interpretation.ConfidenceMillis,
})
}, iterativeRetriever)
cancelAnswer()
if answerErr == nil && stableKnowledgeAnswer(answer) {
planningReply = answer
+43 -3
View File
@@ -59,14 +59,54 @@ type ContextAssemblyTrace struct {
CompilerVersion string
}
type IterativeRetrievalLane string
const (
IterativeRetrievalCurrentFact IterativeRetrievalLane = "CURRENT_FACT"
IterativeRetrievalPositiveExperience IterativeRetrievalLane = "POSITIVE_EXPERIENCE"
IterativeRetrievalNegativeExperience IterativeRetrievalLane = "NEGATIVE_EXPERIENCE"
IterativeRetrievalContradiction IterativeRetrievalLane = "CONTRADICTION"
IterativeRetrievalHistoricalAnalogy IterativeRetrievalLane = "HISTORICAL_ANALOGY"
)
// IterativeRetrievalRequest is the model-visible, storage-agnostic intent.
// Principal, Scope, Focus and policy are deliberately absent: Core binds them
// from the already-authorized request and never accepts them from a model.
type IterativeRetrievalRequest struct {
Query string
RemainingBytes int
Pass int
Query string
SemanticSubject string
Lane IterativeRetrievalLane
Reason string
RemainingBytes int
Pass int
}
// IterativeRetrievalBudget is a Core-enforced per-request budget.
type IterativeRetrievalBudget struct {
MaxRounds int
MaxFragments int
MaxAddedBytes int
MaxLatencyMillis int64
}
// IterativeRetrievalEnvelope is the trusted continuation input assembled by
// Core. It is never produced by a provider or model adapter.
type IterativeRetrievalEnvelope struct {
Request IterativeRetrievalRequest
PrincipalID string
ContextScopeRef string
FocusFrameRef string
TaskRef string
FreshnessRequirement string
DataPolicy capability.DataPolicy
Budget IterativeRetrievalBudget
SeenExcerptIDs []ContextExcerptID
}
type IterativeRetrievalResponse struct {
Candidates []ContextCandidate
Exhausted bool
Reason string
AddedBytes int
}
// CompileContext deterministically selects permitted, relevant excerpts for a
+14 -12
View File
@@ -69,18 +69,19 @@ type AdvisoryRequest struct {
// GatewayAdvisoryRequest describes the task and policy requirement. It cannot
// name a provider, model or binding; Capability owns that selection.
type GatewayAdvisoryRequest struct {
Version ContractVersion
RequestID RequestID
ActorID identity.ActorID
PrincipalID identity.PrincipalID
ScopeID authority.ScopeID
Message string
Context []ContextExcerpt
Locale string
MaxOutputBytes int
DataPolicy capability.DataPolicy
EgressAcknowledged bool
Meaning TaskMeaning
Version ContractVersion
RequestID RequestID
ActorID identity.ActorID
PrincipalID identity.PrincipalID
ScopeID authority.ScopeID
Message string
Context []ContextExcerpt
Locale string
MaxOutputBytes int
DataPolicy capability.DataPolicy
EgressAcknowledged bool
AllowIterativeRetrieval bool
Meaning TaskMeaning
}
// TaskMeaning is the trusted semantic summary produced by Core. It is not a
@@ -177,4 +178,5 @@ type GatewayAdvisoryResponse struct {
ReferencedExcerptIDs []ContextExcerptID
FinishReason FinishReason
Usage *ProviderUsage
IterativeRetrieval *IterativeRetrievalRequest
}
+1 -1
View File
@@ -1 +1 @@
{"task_file":"dev/tasks/GEN2-EVOLVE-001-adaptive-memory-freshness-salience-and-experience.json","task_id":"GEN2-EVOLVE-001","task_revision":3}
{"task_file":"dev/tasks/GEN2-EVOLVE-002-iterative-retrieval-and-cognitive-memory-continuation.json","task_id":"GEN2-EVOLVE-002","task_revision":2}
@@ -0,0 +1,23 @@
{
"task_id": "GEN2-EVOLVE-002",
"task_revision": 2,
"architecture_revision": "ARCH-GEN2-000",
"base_repo_revision": "gen2-integration",
"risk_tier": "E",
"intent": "Wire one bounded cognitive memory-continuation round through the existing Interaction/Epistemics retrieval fabric and Context Compiler without a new Memory owner, V1 retirement, UI change, direct model storage access or an unrestricted memory tool.",
"requirement_ids": ["REQ-GEN2-EVOLVE-002-ITERATIVE-RETRIEVAL"],
"allowed_change_scope": {"modules": ["cognitive contracts", "core-runtime", "cognitive-worker", "Gen2 governance and evidence"], "paths": ["contracts/cognitive/context_compiler.go", "contracts/cognitive/context_compiler_v2_test.go", "contracts/cognitive/types.go", "cmd/core-runtime/cognitive_interpretation.go", "cmd/core-runtime/cognitive_interpretation_test.go", "cmd/core-runtime/main.go", "cmd/cognitive-worker/gateway.go", "cmd/cognitive-worker/gateway_registry_test.go", "dev/ACTIVE_TASK.json", "dev/tasks/GEN2-EVOLVE-002-iterative-retrieval-and-cognitive-memory-continuation.json", "docs/evidence/**", "docs/PROJECT_STATUS.md"]},
"forbidden_change_scope": {"paths": ["architecture/**", "security/**", "go.mod", "go.sum", "database/**", "deploy/**", "cmd/supervisor/**", "cmd/creator-web/**", "PersonalMemory write paths", "GEN2-028", "GEN2-029", "GEN2-030", "Gen3/**"]},
"allowed_architecture_delta": {"new_cross_module_dependencies": 0, "new_canonical_owners": 0, "new_privileges": 0, "new_third_party_dependencies": 0, "new_database_owner_roles": 0},
"contracts": {"consume": ["existing Interaction and Epistemics retrieval projections", "existing Context Compiler V2"], "may_change": ["Cognitive iterative-retrieval request, inherited continuation envelope and advisory response fields only"]},
"semantic_owner": "Interaction remains canonical owner of utterance assertions and ProjectReference; Epistemics remains canonical owner of experience. Core validates and binds continuation identity/scope/policy before calling existing owner reads; Context Compiler remains the only context selector; cognitive-worker may emit only a typed retrieval intent and never reads storage.",
"canonical_state": ["no new state is written", "no new Memory owner", "no direct model or cognitive-worker storage access", "no cross-owner SQL join"],
"persistence_and_migrations": ["no schema or migration change"],
"side_effects_and_privileges": ["no external mutation", "no provider override", "no UI/browser change", "no V1 retirement"],
"state_transitions": ["initial Context Compiler context -> cognitive-worker may emit a typed retrieval intent -> Core binds inherited Principal/Scope/Focus/task/freshness/policy -> existing owner retrieval hard-filters scope before ranking -> Context Compiler compiles unique bounded additions -> one continuation advisory request", "no result -> explicit no-additional-evidence continuation", "contradiction or negative experience is lane-classified rather than suppressed"],
"failure_retry_restart": ["one bounded continuation round only", "round/bytes/fragments/latency and duplicate budgets fail closed", "missing evidence remains unknown", "continuation state is request-scoped; durable Work/Investigation remains responsible for any restartable task"],
"assumptions": ["the task branch starts at gen2-integration 850fe92", "UI/browser acceptance remains deferred by owner", "the traced existing IterativeRetrievalRequest lacks the required structured lane/reason and inherited runtime envelope, so this revision changes only those Cognitive contract fields"],
"acceptance_scenarios": ["EVOLVE-002-SCN-01 insufficient initial context can obtain one additional current fact", "EVOLVE-002-SCN-02 negative and contradiction lanes are separately requested", "EVOLVE-002-SCN-03 inherited Company A scope rejects Company B regardless of request", "EVOLVE-002-SCN-04 duplicate excerpts and rounds/byte limits are rejected", "EVOLVE-002-SCN-05 no result preserves an explicit unknown/no-additional-evidence state"],
"required_tests": ["FAST: targeted go test and go vet for changed cognitive contract, core-runtime and cognitive-worker packages", "FAST: devctl preflight, check-diff, independent governance, git diff --check", "MILESTONE: changed owner/runtime tests plus focused bounded continuation and restart-state check; no browser or FULL suite"],
"terminal_gap_states": ["BLOCKED_BY_SPEC_GAP", "BLOCKED_BY_ARCHITECTURE_GAP", "BLOCKED_BY_MISSING_CAPABILITY", "BLOCKED_BY_CONFLICT", "REQUIRES_MIGRATION_DECISION"]
}
+2
View File
@@ -2,6 +2,8 @@
## Gen2 M6 checkpoint — 2026-08-30
- `GEN2-EVOLVE-002 = MILESTONE_ACCEPTED`: one bounded typed cognitive memory-continuation round is available through Core and the existing retrieval fabric. Evidence: `docs/evidence/gen2-evolve-002-milestone-2026-08-30.md`. UI and V1 compatibility remain unchanged.
- `GEN2-EVOLVE-001 = MILESTONE_ACCEPTED`: bounded adaptive retrieval adds task-relative freshness, computed salience, positive/negative/contradiction experience lanes and provenance-preserving consolidation candidates. Evidence: `docs/evidence/gen2-evolve-001-milestone-2026-08-30.md`. No V1 path was retired and UI/browser acceptance remains deferred by owner.
- `GEN2-028 = COMPLETED_WITH_EVIDENCE`: V1 compatibility inventory is recorded in `docs/evidence/gen2-028-v1-compatibility-inventory-2026-08-30.json`. Current production callers remain for PersonalMemory fallback, ActiveProjectFocus, truth_bundles, ordinary ENV reads, SkillProfile/FastPath; no path was proven `SAFE_TO_RETIRE`.
@@ -0,0 +1,14 @@
# GEN2-EVOLVE-002 — Iterative Retrieval & Cognitive Memory Continuation
- Task/revision: `GEN2-EVOLVE-002 r2`; base `850fe92`.
- Continuation path: Core initial Context Compiler -> Cognitive Gateway advisory -> typed retrieval intent -> Core-bound inherited scope/policy -> existing Interaction/Epistemics retrieval -> existing Context Compiler -> one final advisory.
- Initiator: the cognitive strategy may emit a strict typed lane intent. Gateway validates it; Core, not the model, binds Principal, ContextScope, Focus, task label, freshness policy and local-only data policy.
- Limits: one continuation round, two excerpts, 1024 added bytes, 1.5 second local retrieval window, lane-qualified duplicate suppression. The second advisory has continuation disabled.
- No result returns a private `no additional evidence` excerpt for the final reasoning round. Negative experience and contradiction are separate lanes; scope is hard-filtered before ranking.
- No new owner, schema, provider selector, direct cognitive-worker storage access, V1 retirement or UI change.
## Verification
MILESTONE gate: targeted `go test` and `go vet` for `contracts/cognitive`, `cmd/cognitive-worker`, and `cmd/core-runtime`; `devctl preflight`, `check-diff`, `independent`, `check-architecture`, and `git diff --check`.
Focused tests cover the second retrieval round, Company A/B isolation, negative and contradiction lanes, duplicate suppression, byte/round limits, and no-additional-evidence behavior. Continuation state is request-scoped; durable restart/resume remains with the existing Work/Investigation owner.