Documentation
¶
Overview ¶
Controller adapts the compaction service to the step-loop task seam.
Package compaction summarizes a session's older history into a state record when the context window fills, keeping the newest messages verbatim.
The compaction service runs one compaction boundary end to end. Concrete config/provider/plugin/session/processor services are represented by narrow interfaces; the state transitions and model-visible strings live here.
Post-compaction message surgery: the overflow history selection, the replay of the message that overflowed, and the auto-continue text. These helpers are pure apart from the injected ID factory.
Index ¶
- Constants
- Variables
- func BuildAuthoritativeTaskPin(task, source string) string
- func BuildChangedFilesPin(lines []string) string
- func BuildPrompt(previousSummary *string, context []string) string
- func CapTranscript(transcript string, maxChars float64) (string, float64)
- func ClassifySummaryFailure(message msgmodel.WithParts) string
- func EvidenceBlocksFromMessages(messages []msgmodel.WithParts, maxToolChars ...float64) []string
- func HeadTailTruncate(text string, maxChars float64) string
- func IsSyntheticUser(message msgmodel.WithParts) bool
- func NormalizeOffFormatSummary(message msgmodel.WithParts) (string, bool)
- func SerializeTranscript(messages []msgmodel.WithParts, maxToolChars float64) string
- func TailBudget(cfg overflow.Config, marks overflow.CompactionWatermarks) float64
- func ValidateSummary(message msgmodel.WithParts) error
- func ValidateSummaryText(text string) error
- type Agent
- type AgentProvider
- type AgentProviderFunc
- type AutoContinueInput
- type CompactingResult
- type CompactionDecision
- type CompletedCompaction
- type ConfigProvider
- type ConfigProviderFunc
- type ContextCapacityError
- type ContextSizer
- type ContextSizerFunc
- type Controller
- func (c Controller) CreateCompaction(ctx context.Context, sessionID string, user msgmodel.User, overflowed bool) error
- func (c Controller) IsOverflow(ctx context.Context, assistant msgmodel.Assistant, model steploop.Model) (bool, error)
- func (c Controller) ProcessCompaction(ctx context.Context, input steploop.TaskInput, task msgmodel.CompactionPart) (steploop.Result, error)
- func (c Controller) Prune(ctx context.Context, sessionID string) error
- type CreateInput
- type DecisionSink
- type DecisionSinkFunc
- type Dependencies
- type EstimateFunc
- type EventSink
- type EvidenceSelector
- type EvidenceSelectorFunc
- type FallbackEvidenceSelector
- type InstanceContext
- type Model
- type ModelProvider
- type ModelRef
- type OverflowHistory
- type Plugin
- type ProcessInput
- type ProcessorFactory
- type ProcessorFactoryFunc
- type ProviderInfo
- type Replay
- type Service
- func (s *Service) Create(ctx context.Context, input CreateInput) error
- func (s *Service) Estimate(messages []msgmodel.WithParts, model Model) (float64, error)
- func (s *Service) IsOverflow(ctx context.Context, tokens msgmodel.Tokens, model Model) (bool, error)
- func (s *Service) Process(ctx context.Context, input ProcessInput) (steploop.Result, error)
- func (s *Service) Prune(ctx context.Context, sessionID string) error
- type SummaryProcessor
- type SummaryRequest
- type Turn
Constants ¶
const ( PruneMinimum = 20_000 PruneProtect = 40_000 // ToolOutputMaxChars caps each tool output inside the flattened transcript // handed to the summarizer. ToolOutputMaxChars = 2_000 // SummaryTranscriptMaxChars bounds the whole flattened transcript. A head // that outgrows it is cut in the middle (a quarter from the start, the rest // from the end) so the summarizer sees how the work began and, mostly, its // latest state. ~60K tokens, well inside any current model's window. SummaryTranscriptMaxChars = 240_000 EvidenceToolOutputMaxChars = 2_000 EventCompacted = "session.compacted" )
const ( SummaryClassToolCall = "tool-call" SummaryClassDSMLText = "dsml-text" SummaryClassEmpty = "empty" SummaryClassFormat = "contract-format" )
Summary failure classes, emitted on the compaction decision event so every rejected summary can be attributed from the event stream.
const ( // DefaultPreserveRecentFraction sizes the token budget for the truncated // older messages of the tail as a fraction of the high watermark, so the // tail grows with the budget. The newest message is kept on top of it. DefaultPreserveRecentFraction = 0.2 // TailToolOutputMaxChars caps each completed tool output of an OLDER tail // message. Head/tail truncation keeps the start and, mostly, the end, so // an error at the bottom of a test run survives. TailToolOutputMaxChars = 4_000 // TailReasoningMaxChars caps each unsigned reasoning part of EVERY tail // message to its head. TailReasoningMaxChars = 1_000 )
The verbatim tail a compaction keeps ahead of the summary.
The newest messages are kept verbatim at every boundary and only what lies before them is summarized. Old tool outputs INSIDE the tail are truncated in the store, so a tail always fits: measured on untruncated multi-kilobyte command outputs, one assistant message could cost more than the whole budget and no split point would ever be found.
Reasoning is cut too, in every kept message including the newest. The same-model projection re-sends every reasoning part with its provider metadata, and a single message can carry far more reasoning than tool traffic. Reasoning is the model's scratch, not state; signed reasoning (Anthropic) is left alone because the provider validates it byte for byte.
const SummarySystemPrompt = `` /* 357-byte string literal not displayed */
const SummaryTemplate = `` /* 507-byte string literal not displayed */
The task itself is pinned verbatim beside every summary, so the record does not repeat it. The contract is deliberately small: current work, exact verification evidence, and the next concrete action.
Variables ¶
var ErrContextCapacityExhausted = errors.New("compaction: context capacity exhausted")
var PruneProtectedTools = []string{"skill"}
Functions ¶
func BuildChangedFilesPin ¶
BuildChangedFilesPin renders the code-computed changed-files record that is pinned beside every summary: a generated summary can forget a file, a diffstat cannot.
func BuildPrompt ¶
func CapTranscript ¶
CapTranscript bounds a flattened transcript to maxChars, cutting the middle so the beginning and the latest state both survive. It returns the number of characters removed so the boundary can report it.
func ClassifySummaryFailure ¶
ClassifySummaryFailure labels a rejected summary attempt. tool-call is a structural ToolPart; dsml-text is provider tool-call markup leaked as TEXT; empty is no usable text; contract-format is any other heading/structure violation.
func HeadTailTruncate ¶
func IsSyntheticUser ¶
func NormalizeOffFormatSummary ¶
NormalizeOffFormatSummary recovers a stateful answer (headed "Summary of Changes" or similar) that would otherwise be discarded solely because it missed the report template. Coding continuations and tool calls still fail closed; only an explicitly summary/state-shaped text is wrapped.
func SerializeTranscript ¶
SerializeTranscript flattens the conversation being summarized into ONE plain-text block for the summary model call (the caller wraps it in <conversation> tags). Handing the summarizer the session as a live message array invites the model to keep coding — leaking tool-call markup as text or narrating its next action — instead of serializing state. A flattened transcript cannot be "continued": there is no open tool call, no assistant turn to extend, only data to read.
Each tool output is head/tail truncated (HeadTailTruncate) so the serialized block is bounded and, in practice, smaller than the message array it replaces.
func TailBudget ¶
func TailBudget(cfg overflow.Config, marks overflow.CompactionWatermarks) float64
tailBudget is the token budget for the truncated older messages of the verbatim tail. An explicit preserve_recent_tokens always wins. Otherwise the tail is preserve_recent_fraction (default DefaultPreserveRecentFraction) of the high watermark, so a 300K trigger keeps a 60K tail rather than a fixed one.
func ValidateSummary ¶
ValidateSummary rejects a response before it can become a compaction boundary. Structural step/reasoning parts are harmless provider metadata, but a tool-shaped response is never a serialized session state.
func ValidateSummaryText ¶
Types ¶
type AgentProvider ¶
type AgentProviderFunc ¶
type AutoContinueInput ¶
type CompactingResult ¶
type CompactionDecision ¶
type CompactionDecision struct {
SessionID string `json:"sessionID"`
Status string `json:"status"`
Before float64 `json:"beforeTokens"`
After float64 `json:"afterTokens"`
Capacity float64 `json:"capacityTokens"`
Low float64 `json:"lowTokens"`
High float64 `json:"highTokens"`
// DroppedTail: the watermark rebuild removed the verbatim tail.
// StubbedSummary: it also had to replace the summary with the capacity
// stub because the summary block alone did not fit.
DroppedTail bool `json:"droppedTail"`
StubbedSummary bool `json:"stubbedSummary,omitempty"`
// SummaryStatus is valid, normalized, fallback, no-head (nothing to
// summarize, no call made), overflow (the summary request itself exceeded
// the model), summary-error (the call failed), summary-stopped, or
// capacity-fallback (the watermark rebuild replaced the record). Every
// status except valid and normalized installs the deterministic record.
SummaryStatus string `json:"summaryStatus"`
SummaryClass string `json:"summaryClass,omitempty"`
SummaryError string `json:"summaryError,omitempty"`
TranscriptMessages int `json:"transcriptMessages"`
TranscriptChars int `json:"transcriptChars"`
TranscriptCutChars float64 `json:"transcriptCutChars,omitempty"`
PromptChars int `json:"promptChars"`
SummaryPromptTokens uint64 `json:"summaryPromptTokens"`
SummaryOutputTokens uint64 `json:"summaryOutputTokens"`
SummaryWallMs int64 `json:"summaryWallMs"`
TailBudget float64 `json:"tailBudget"`
TailMessages int `json:"tailMessages"`
TailTokens float64 `json:"tailTokens"`
TailTruncatedOutputs int `json:"tailTruncatedOutputs"`
TailTruncatedReasoning int `json:"tailTruncatedReasoning,omitempty"`
// PreviousSummaryCarried is set when a deterministic record carried the
// previous boundary's summary forward verbatim.
PreviousSummaryCarried bool `json:"previousSummaryCarried,omitempty"`
}
CompactionDecision is the one record a boundary leaves behind: what the summarizer was handed (transcript and prompt sizes), what the provider reported back (prompt and output tokens), how the summary was judged, and how much verbatim tail was kept.
type CompletedCompaction ¶
type ConfigProvider ¶
type ConfigProviderFunc ¶
type ContextCapacityError ¶
func (ContextCapacityError) Error ¶
func (err ContextCapacityError) Error() string
func (ContextCapacityError) Unwrap ¶
func (ContextCapacityError) Unwrap() error
type ContextSizer ¶
type ContextSizer interface {
EstimateContext(ctx context.Context, messages []msgmodel.WithParts, model Model) (float64, error)
}
ContextSizer measures the complete model-visible request represented by a projected message list. The senior-dev adapter includes its system prompt and tool schemas; the default service implementation measures messages alone.
type ContextSizerFunc ¶
type ContextSizerFunc func( ctx context.Context, messages []msgmodel.WithParts, model Model, ) (float64, error)
func (ContextSizerFunc) EstimateContext ¶
type Controller ¶
type Controller struct {
Compaction *Service
}
func (Controller) CreateCompaction ¶
func (Controller) IsOverflow ¶
func (Controller) ProcessCompaction ¶
func (c Controller) ProcessCompaction( ctx context.Context, input steploop.TaskInput, task msgmodel.CompactionPart, ) (steploop.Result, error)
type CreateInput ¶
type DecisionSink ¶
type DecisionSink interface {
CompactionDecision(decision CompactionDecision)
}
type DecisionSinkFunc ¶
type DecisionSinkFunc func(decision CompactionDecision)
func (DecisionSinkFunc) CompactionDecision ¶
func (f DecisionSinkFunc) CompactionDecision(decision CompactionDecision)
type Dependencies ¶
type Dependencies struct {
Store steploop.Store
Config ConfigProvider
Agents AgentProvider
Provider ModelProvider
Plugin Plugin
Processors ProcessorFactory
Evidence EvidenceSelector
Sizer ContextSizer
Decisions DecisionSink
Events EventSink
Instance InstanceContext
// ChangedFiles reports the workspace's changed files as preformatted
// lines, computed by code (a diffstat against the starting tree plus the
// status). It is pinned beside every summary as a record the model cannot
// misremember; nil disables the pin.
ChangedFiles func(ctx context.Context) []string
NewID func(prefix string) string
Now func() uint64
}
type EstimateFunc ¶
type EvidenceSelector ¶
type EvidenceSelectorFunc ¶
func (EvidenceSelectorFunc) SelectEvidence ¶
type FallbackEvidenceSelector ¶
type FallbackEvidenceSelector struct {
MaxChars float64
}
FallbackEvidenceSelector exposes the deterministic evidence harvest through the compaction service's evidence seam.
func (FallbackEvidenceSelector) SelectEvidence ¶
type InstanceContext ¶
type ModelProvider ¶
type OverflowHistory ¶
type ProcessInput ¶
type ProcessorFactory ¶
type ProcessorFactoryFunc ¶
type ProviderInfo ¶
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
func NewService ¶
func NewService(deps Dependencies) *Service