mirror of
https://github.com/esengine/DeepSeek-Reasonix.git
synced 2026-11-11 23:53:19 -10:00
Problem: Compaction entry points relabelled image, extension, persistence and validation failures as summary errors. An expired safe-pressure operation could replace a later error with its budget timeout and discard recovery guidance. Root cause: The shared finish wrapper classified every returned error using the work context state instead of the owner and identity of the failure. Fix: Classify provider failures only at the summary request and known summary outcomes at their owner. Normalize deadlines only when the returned error and work budget both identify expiry. Preserve wrapped causes and apply localized context recovery guidance last. Restore oversized protected tool-result coverage through successful summarization without truncation. Verification: Regression tests reproduce missing-image classification, safe timeout followed by extension blocks, persistence identity, compress validation and localized hard-limit recovery. Check actual summary failure typing and the protected-result replay with intact canonical history. Run focused race tests, pinned lint, repolint and the complete root suite; track exact-head PR CI.
147 lines
6.1 KiB
Go
147 lines
6.1 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"reasonix/internal/provider"
|
|
)
|
|
|
|
type summaryProjectionCommit struct {
|
|
canonical, fold, projected []provider.Message
|
|
result foldSummary
|
|
transcriptVersion, projectionVersion, generation uint64
|
|
activeTurn int64
|
|
trigger, summary, inputHash, outputHash string
|
|
sourceTokens, projectionTokens int
|
|
// covered is the canonical length the frozen projection body represents;
|
|
// messages past it splice live from the transcript.
|
|
covered int
|
|
}
|
|
|
|
// commitSummaryProjection CAS-installs a checkpoint under compactionMu:
|
|
// transcript version/hash, projection version, and generation must still match.
|
|
// The maintenance event is emitted only after the lock is released so a sink
|
|
// that re-enters ContextMaintenanceSnapshot cannot deadlock.
|
|
func (a *Agent) commitSummaryProjection(ctx context.Context, commit summaryProjectionCommit) (CompactionState, error) {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
if err := ctx.Err(); err != nil {
|
|
return CompactionState{}, err
|
|
}
|
|
state := a.summaryProjectionState(commit)
|
|
a.sess.compactionMu.Lock()
|
|
// This is the shared commit boundary for ordinary, positional, and fallback
|
|
// summary projection installs. Cancellation that wins before this point
|
|
// prevents every variant from publishing a late summary.
|
|
if err := ctx.Err(); err != nil {
|
|
a.sess.compactionMu.Unlock()
|
|
return CompactionState{}, err
|
|
}
|
|
current, currentVersion := a.sess.conversation.snapshotMessagesVersion()
|
|
if currentVersion != commit.transcriptVersion ||
|
|
len(current) != len(commit.canonical) ||
|
|
coveredPrefixHash(current, len(current)) != coveredPrefixHash(commit.canonical, len(commit.canonical)) ||
|
|
a.sess.compactionState.Projection.ProjectionVersion != commit.projectionVersion ||
|
|
a.sess.compactionState.Generation != commit.generation {
|
|
a.sess.compactionMu.Unlock()
|
|
return CompactionState{}, summaryError(errCompressStaleContext)
|
|
}
|
|
prev := a.sess.compactionState
|
|
a.sess.compactionState = state
|
|
// After installation begins we finish its consistency and durability work;
|
|
// cancellation may prevent a later batch but cannot tear this batch in half.
|
|
accepted, err := a.persistInstalledProjectionLocked(context.Background(), state, current)
|
|
if err != nil {
|
|
if accepted {
|
|
a.sess.checkpointState = "pending"
|
|
a.sess.compactionMu.Unlock()
|
|
return CompactionState{}, &compactionPersistenceError{fmt.Errorf("persist projection: %w", err)}
|
|
}
|
|
a.sess.compactionState = prev
|
|
a.sess.compactionMu.Unlock()
|
|
if errors.Is(err, errCompressStaleContext) {
|
|
return CompactionState{}, err
|
|
}
|
|
return CompactionState{}, &compactionPersistenceError{fmt.Errorf("persist projection: %w", err)}
|
|
}
|
|
a.sess.checkpointState = "applied"
|
|
if commit.activeTurn != 0 && commit.trigger != CompactionTriggerManual {
|
|
a.sess.compaction.lastTurn.Store(commit.activeTurn)
|
|
}
|
|
receipt := state.LastReceipt
|
|
a.sess.compactionMu.Unlock()
|
|
a.emitContextMaintenance(receipt)
|
|
return state, nil
|
|
}
|
|
|
|
func (a *Agent) persistInstalledProjectionLocked(ctx context.Context, state CompactionState, canonical []provider.Message) (bool, error) {
|
|
accepted := false
|
|
if recorder, ok := a.svc.sessionCheckpointer.(SessionModelContextRecorder); ok {
|
|
visible := modelVisibleFromProjection(state.Projection, canonical)
|
|
commit := cloneSessionModelContextCommit(SessionModelContextCommit{
|
|
OperationID: state.LastReceipt.OperationID,
|
|
Reason: state.LastReceipt.Action,
|
|
Messages: visible,
|
|
})
|
|
result, err := recorder.RecordSessionModelContext(ctx, commit)
|
|
accepted = result.Accepted
|
|
if err != nil {
|
|
if accepted {
|
|
a.sess.pendingModelContextCommit = &commit
|
|
}
|
|
return accepted, err
|
|
}
|
|
if result.Accepted && !result.Durable {
|
|
a.sess.pendingModelContextCommit = &commit
|
|
return true, errors.New("model context commit was accepted but is not durable")
|
|
}
|
|
}
|
|
if err := a.persistCompactionStateLocked(); err != nil {
|
|
if accepted {
|
|
visible := modelVisibleFromProjection(state.Projection, canonical)
|
|
commit := cloneSessionModelContextCommit(SessionModelContextCommit{
|
|
OperationID: state.LastReceipt.OperationID,
|
|
Reason: state.LastReceipt.Action,
|
|
Messages: visible,
|
|
})
|
|
a.sess.pendingModelContextCommit = &commit
|
|
}
|
|
return accepted, err
|
|
}
|
|
a.sess.pendingModelContextCommit = nil
|
|
return accepted, nil
|
|
}
|
|
|
|
func (a *Agent) summaryProjectionState(commit summaryProjectionCommit) CompactionState {
|
|
projectionVersion := commit.projectionVersion + 1
|
|
now := time.Now().UTC()
|
|
summaryHash := summaryContentHash(commit.summary)
|
|
coveredHash := coveredPrefixHash(commit.canonical, commit.covered)
|
|
receipt := &ContextMaintenanceReceipt{
|
|
OperationID: fmt.Sprintf("summary-%d-%s", projectionVersion, commit.outputHash), Status: "applied",
|
|
Action: "summary", Trigger: commit.trigger, SourceProjection: commit.projectionVersion,
|
|
ProjectionVersion: projectionVersion, CoveredCount: commit.covered, CoveredPrefixHash: coveredHash,
|
|
InputHash: commit.inputHash, OutputHash: commit.outputHash, InputTokens: commit.sourceTokens,
|
|
ResultTokens: commit.projectionTokens, SavedTokens: max(0, commit.sourceTokens-commit.projectionTokens),
|
|
SummaryHash: summaryHash, CacheBreak: true, CreatedAt: now,
|
|
}
|
|
// LastReceipt is authoritative; do not mirror last_trigger/last_mode/token
|
|
// counters or top-level blocked_* fields (stripped again on save).
|
|
return CompactionState{
|
|
SchemaVersion: compactionStateSchemaCurrent, TranscriptVersion: commit.transcriptVersion,
|
|
Generation: commit.generation + 1, PromptCacheKey: a.currentPromptCacheKey(),
|
|
Projection: ContextProjection{
|
|
Messages: commit.projected, TranscriptVersion: commit.transcriptVersion,
|
|
ProjectionVersion: projectionVersion, CoveredCount: commit.covered, CoveredPrefixHash: coveredHash,
|
|
PinnedContextHash: pinnedContextCoverageHash(commit.canonical, commit.covered),
|
|
SummaryHash: summaryHash, SourceTokens: commit.sourceTokens, ProjectionTokens: commit.projectionTokens,
|
|
ViewInputHash: commit.inputHash, ViewOutputHash: commit.outputHash, CreatedAt: now,
|
|
},
|
|
LastReceipt: receipt, UpdatedAt: now,
|
|
}
|
|
}
|