| 1 | package agent |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "fmt" |
| 7 | "time" |
| 8 | |
| 9 | "reasonix/internal/provider" |
| 10 | ) |
| 11 | |
| 12 | // maintenanceInstall is one free projection rewrite (no summarizer call): the |
| 13 | // visible view it started from and the projected view replacing it. |
| 14 | type maintenanceInstall struct { |
| 15 | trigger, action string |
| 16 | state CompactionState |
| 17 | canonical []provider.Message |
| 18 | transcriptVersion uint64 |
| 19 | visible, projected []provider.Message |
| 20 | affected int |
| 21 | } |
| 22 | |
| 23 | // installMaintenanceProjection CAS-installs a free projection under |
| 24 | // compactionMu. The caller owns compactionRunMu for the whole maintenance run; |
| 25 | // canonical storage, including RawContent, is never modified. |
| 26 | func (a *Agent) installMaintenanceProjection(in maintenanceInstall) (bool, error) { |
| 27 | projected := projectionMessagesPreservingPinnedContext(in.projected) |
| 28 | projected, _, err := rebasePinnedContextProjection(projected, in.canonical, len(in.canonical)) |
| 29 | if err != nil { |
| 30 | return false, err |
| 31 | } |
| 32 | sourceTokens := a.estimatedVisibleRequestTokens(in.visible) |
| 33 | resultTokens := a.estimatedVisibleRequestTokens(projected) |
| 34 | inputHash := a.contextMaintenanceInputHash(modelInputMessages(in.visible)) |
| 35 | outputHash := providerVisibleFingerprint(modelInputMessages(projected)) |
| 36 | projectionVersion := in.state.Projection.ProjectionVersion + 1 |
| 37 | now := time.Now().UTC() |
| 38 | coveredHash := coveredPrefixHash(in.canonical, len(in.canonical)) |
| 39 | receipt := &ContextMaintenanceReceipt{ |
| 40 | OperationID: fmt.Sprintf("%s-%d-%s", in.action, projectionVersion, outputHash), Status: "applied", Action: in.action, |
| 41 | Trigger: in.trigger, SourceProjection: in.state.Projection.ProjectionVersion, ProjectionVersion: projectionVersion, |
| 42 | CoveredCount: len(in.canonical), CoveredPrefixHash: coveredHash, InputHash: inputHash, OutputHash: outputHash, |
| 43 | InputTokens: sourceTokens, ResultTokens: resultTokens, SavedTokens: max(0, sourceTokens-resultTokens), |
| 44 | AffectedToolResults: in.affected, CacheBreak: true, CreatedAt: now, |
| 45 | } |
| 46 | next := in.state |
| 47 | next.SchemaVersion = compactionStateSchemaCurrent |
| 48 | next.TranscriptVersion = in.transcriptVersion |
| 49 | next.Generation++ |
| 50 | next.PromptCacheKey = a.currentPromptCacheKey() |
| 51 | next.Projection = ContextProjection{ |
| 52 | Messages: projected, TranscriptVersion: in.transcriptVersion, ProjectionVersion: projectionVersion, |
| 53 | CoveredCount: len(in.canonical), CoveredPrefixHash: coveredHash, SourceTokens: sourceTokens, |
| 54 | PinnedContextHash: pinnedContextCoverageHash(in.canonical, len(in.canonical)), |
| 55 | ProjectionTokens: resultTokens, ViewInputHash: inputHash, ViewOutputHash: outputHash, CreatedAt: now, |
| 56 | } |
| 57 | next.LastReceipt = receipt |
| 58 | next.UpdatedAt = now |
| 59 | |
| 60 | a.sess.compactionMu.Lock() |
| 61 | current, currentVersion := a.sess.conversation.snapshotMessagesVersion() |
| 62 | if currentVersion != in.transcriptVersion || len(current) != len(in.canonical) || |
| 63 | coveredPrefixHash(current, len(current)) != coveredHash || |
| 64 | a.sess.compactionState.Projection.ProjectionVersion != in.state.Projection.ProjectionVersion || |
| 65 | a.sess.compactionState.Generation != in.state.Generation { |
| 66 | a.sess.compactionMu.Unlock() |
| 67 | return false, errCompressStaleContext |
| 68 | } |
| 69 | previous := a.sess.compactionState |
| 70 | a.sess.compactionState = next |
| 71 | accepted, err := a.persistInstalledProjectionLocked(context.Background(), next, current) |
| 72 | if err != nil { |
| 73 | if accepted { |
| 74 | a.sess.checkpointState = "pending" |
| 75 | a.sess.compactionMu.Unlock() |
| 76 | return false, fmt.Errorf("persist %s projection: %w", in.action, err) |
| 77 | } |
| 78 | a.sess.compactionState = previous |
| 79 | a.sess.compactionMu.Unlock() |
| 80 | if errors.Is(err, errCompressStaleContext) { |
| 81 | return false, err |
| 82 | } |
| 83 | return false, fmt.Errorf("persist %s projection: %w", in.action, err) |
| 84 | } |
| 85 | a.sess.checkpointState = "applied" |
| 86 | a.sess.compactionMu.Unlock() |
| 87 | a.emitContextMaintenance(receipt) |
| 88 | return true, nil |
| 89 | } |
| 90 |