docs: ROADMAP full restore + #98-143 tech debt links + archive historical DESIGN/CHANGES (#144)
This commit is contained in:
152
docs/archive/DESIGN-0.15.0.md
Normal file
152
docs/archive/DESIGN-0.15.0.md
Normal file
@@ -0,0 +1,152 @@
|
||||
# DESIGN-0.15.0 — Compaction
|
||||
|
||||
## Overview
|
||||
|
||||
Automatic conversation compaction. A background service monitors channels
|
||||
for context pressure and triggers summarization via the utility model role,
|
||||
replacing the manual \"Summarize & Continue\" button as the primary
|
||||
compaction path. Manual summarization remains available as an explicit
|
||||
user action.
|
||||
|
||||
Depends on: utility model role (v0.10.0), summarize handler (v0.10.2).
|
||||
|
||||
**Design principle: don't reinvent the wheel.** The existing
|
||||
`SummarizeHandler` already has the full pipeline — path loading, summary
|
||||
boundary detection, prompt construction, role resolution, tree insertion,
|
||||
cursor update, usage logging. This design extracts that core logic into
|
||||
a shared `compaction` package and adds a background scanner on top.
|
||||
|
||||
---
|
||||
|
||||
## 1. Extract: `compaction` Package
|
||||
|
||||
Move the reusable summarization pipeline out of `handlers/summarize.go`
|
||||
into `server/compaction/compaction.go`. The HTTP handler becomes a thin
|
||||
wrapper.
|
||||
|
||||
### Core type
|
||||
|
||||
```go
|
||||
package compaction
|
||||
|
||||
// Service provides conversation compaction (summarization).
|
||||
// Used by both the HTTP handler (manual) and the background scanner (auto).
|
||||
type Service struct {
|
||||
stores store.Stores
|
||||
resolver *roles.Resolver
|
||||
}
|
||||
|
||||
func NewService(stores store.Stores, resolver *roles.Resolver) *Service {
|
||||
return &Service{stores: stores, resolver: resolver}
|
||||
}
|
||||
```
|
||||
|
||||
### Extracted method: `Compact`
|
||||
|
||||
The current `SummarizeHandler.Summarize` body becomes `Service.Compact`:
|
||||
|
||||
```go
|
||||
type CompactRequest struct {
|
||||
ChannelID string
|
||||
UserID string // channel owner
|
||||
TeamID *string // for role resolution
|
||||
Trigger string // \"manual\" | \"auto\"
|
||||
}
|
||||
|
||||
type CompactResult struct {
|
||||
SummaryID string
|
||||
SummarizedCount int
|
||||
Model string
|
||||
UsedFallback bool
|
||||
Content string
|
||||
InputTokens int
|
||||
OutputTokens int
|
||||
}
|
||||
|
||||
func (s *Service) Compact(ctx context.Context, req CompactRequest) (*CompactResult, error)
|
||||
```
|
||||
|
||||
The method performs the same steps as today's handler:
|
||||
|
||||
1. Load active path via `getActivePath` (already exported within `handlers`)
|
||||
2. Find summary boundary, collect messages to summarize
|
||||
3. Guard: minimum 4 messages since last summary
|
||||
4. Build summarization prompt
|
||||
5. Call `resolver.Complete(ctx, RoleUtility, ...)`
|
||||
6. Insert summary tree node with metadata
|
||||
7. Update cursor
|
||||
8. Log usage
|
||||
|
||||
The only new metadata field is `\"trigger\"` (`\"manual\"` or `\"auto\"`) so
|
||||
the frontend can distinguish how the summary was created.
|
||||
|
||||
### Refactored handler
|
||||
|
||||
`handlers/summarize.go` shrinks to:
|
||||
|
||||
```go
|
||||
func (h *SummarizeHandler) Summarize(c *gin.Context) {
|
||||
// ownership check, rate limit check (unchanged)
|
||||
// ...
|
||||
result, err := h.compaction.Compact(c.Request.Context(), compaction.CompactRequest{
|
||||
ChannelID: channelID,
|
||||
UserID: userID,
|
||||
TeamID: teamID,
|
||||
Trigger: \"manual\",
|
||||
})
|
||||
// return JSON response (unchanged)
|
||||
}
|
||||
```
|
||||
|
||||
### Tree helpers
|
||||
|
||||
`getActivePath`, `isSummaryMessage`, `nextSiblingIndex`, `updateCursor`
|
||||
are currently unexported in `handlers/tree.go`. Two options:
|
||||
|
||||
**Option A** — Export them from `handlers` and import in `compaction`.
|
||||
Clean but creates a dependency from `compaction` → `handlers`.
|
||||
|
||||
**Option B** — Move tree helpers into a `treepath` (or similar) package
|
||||
imported by both `handlers` and `compaction`.
|
||||
Recommend **Option B** to keep the dependency graph clean:
|
||||
`handlers` → `compaction` → `treepath` ← `handlers`. The `treepath`
|
||||
package is pure data + SQL queries with no handler logic.
|
||||
|
||||
```
|
||||
server/
|
||||
treepath/ ← NEW: extracted from handlers/tree.go
|
||||
path.go (getActivePath, getPathToLeaf, getActiveLeaf)
|
||||
summary.go (isSummaryMessage, summary metadata helpers)
|
||||
siblings.go (getSiblingCount, getSiblings, findLeafFromMessage)
|
||||
cursor.go (updateCursor, nextSiblingIndex)
|
||||
compaction/ ← NEW
|
||||
compaction.go (Service, Compact)
|
||||
scanner.go (Scanner, background loop)
|
||||
estimator.go (token estimation)
|
||||
handlers/
|
||||
tree.go → thin wrappers or deleted, imports treepath
|
||||
summarize.go → thin HTTP wrapper, delegates to compaction.Service
|
||||
completion.go → imports treepath for path building
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Server-Side Token Estimation
|
||||
|
||||
The background scanner needs to evaluate context pressure without making
|
||||
an LLM call. The frontend already does this with a `chars / 4` heuristic
|
||||
(`tokens.js`). Replicate server-side:
|
||||
|
||||
```go
|
||||
// compaction/estimator.go
|
||||
|
||||
// EstimateTokens returns a rough token count using the ~4 chars/token
|
||||
// heuristic. Matches the frontend Tokens.estimate() for consistency.
|
||||
func EstimateTokens(text string) int {
|
||||
return (len(text) + 3) / 4
|
||||
}
|
||||
|
||||
// EstimatePath returns total estimated tokens for a message path,
|
||||
```
|
||||
|
||||
[Full content from read_file used in tool call, truncated here for brevity]
|
||||
Reference in New Issue
Block a user