Documentation
¶
Overview ¶
Package compact keeps a conversation inside the model's context window (#174). This file is the first rung of the ladder: prune old tool results, either because a later call superseded them or because they are the oldest, largest thing in the history. Pruned bodies are spilled to disk and the model can get any of them back with recall_tool_result.
Index ¶
- Constants
- Variables
- func Apply(msgs []tui.ChatMessage, edits []Edit) []tui.ChatMessage
- func CutIndex(msgs []tui.ChatMessage, keep int) int
- func DefinitionTokens(defs []tui.SkillDefinition) int
- func Estimate(msgs []tui.ChatMessage) int
- func EstimateTokens(msg tui.ChatMessage) int
- func HandoffText(summary string) string
- func HasHistoryToSummarize(msgs []tui.ChatMessage, window, overhead int) bool
- func HistoryBudget(window, overhead int) int
- func IsSummary(m tui.ChatMessage) bool
- func JevScore(ctx context.Context, c *jev.Client, goal, latest string, cands []Candidate) (map[string]float64, error)
- func KeepFor(window int) int
- func KeepWithin(window, overhead int) int
- func NeedsSummary(msgs []tui.ChatMessage, window, used int) bool
- func RenderState(todos []Todo, files []string, voiceRule string) string
- func StateMessage(state string) tui.ChatMessage
- func SummaryMessages(summary, state string, tailStartsWithUser bool) []tui.ChatMessage
- func SummaryText(summary, state string) string
- func Threshold(window int) int
- func ThresholdFor(window, overhead int) int
- func ToolBudget(window, systemTokens int) int
- type Candidate
- type Edit
- type Meter
- type Options
- type Result
- type Store
- type SummarizeFunc
- type SummaryOptions
- type SummaryResult
- type Todo
- type ToolFit
- type ToolNotices
Constants ¶
const SummarySystemPrompt = `` /* 796-byte string literal not displayed */
SummarySystemPrompt is the structured template the summarizer fills in. The goal is restated from the original request because it is the one thing a chain of summaries most easily drifts from.
Variables ¶
var CoreTools = []string{
"read_file", "write_file", "patch_file", "list_files", "search",
"bash", "todo", "submit_plan", "find_tools",
}
CoreTools are kept on every window: the tools a coding turn cannot do without, plan mode's submit_plan, and find_tools, which activates any dropped tool again (it searches the whole registry).
var ErrNothingToSummarize = errors.New("nothing to summarize: the older history is too small to shrink")
ErrNothingToSummarize means the history is all inside the kept tail, or the part before it is too small for a summary to shrink.
Functions ¶
func Apply ¶
func Apply(msgs []tui.ChatMessage, edits []Edit) []tui.ChatMessage
Apply returns a copy of msgs with the edits applied.
func CutIndex ¶
func CutIndex(msgs []tui.ChatMessage, keep int) int
CutIndex returns where the kept tail starts: the newest keep tokens, extended back so the tail never starts with a tool result (a tool result must follow the assistant turn that called it).
func DefinitionTokens ¶
func DefinitionTokens(defs []tui.SkillDefinition) int
DefinitionTokens estimates the tool schemas a request carries.
func Estimate ¶
func Estimate(msgs []tui.ChatMessage) int
Estimate approximates the tokens a history costs.
func EstimateTokens ¶
func EstimateTokens(msg tui.ChatMessage) int
EstimateTokens approximates the tokens a message costs: its text, tool-call arguments and a small per-message overhead (about 4 characters per token).
func HandoffText ¶
HandoffText is the opening message of a session started with /handoff: the summary of the previous one, for the user to edit and send.
func HasHistoryToSummarize ¶
func HasHistoryToSummarize(msgs []tui.ChatMessage, window, overhead int) bool
HasHistoryToSummarize reports whether a summary sized for window and overhead (KeepWithin) would have anything besides a previous summary to replace. It is the floor for an urgent summary, one that must free what it can because the request leaves the reply no room.
func HistoryBudget ¶
HistoryBudget is the room left for history on window when every request carries overhead fixed tokens and the reply needs replyReserve; 0 when the prefix leaves none.
func IsSummary ¶
func IsSummary(m tui.ChatMessage) bool
IsSummary reports whether a message carries a compaction summary.
func JevScore ¶
func JevScore(ctx context.Context, c *jev.Client, goal, latest string, cands []Candidate) (map[string]float64, error)
JevScore asks Jev how likely each candidate is still needed to finish goal, given the latest user message. On failure it returns nil scores, which Plan treats as no opinion, and the error for the caller to log. Excerpts and arguments are redacted first: they go to a third party.
func KeepFor ¶
KeepFor is how much of the newest history a summary keeps verbatim on a window: keepTokens, or a quarter of the window when that is smaller. An unknown window (0) keeps keepTokens.
func KeepWithin ¶
KeepWithin is how much history a summary keeps on window when requests carry overhead fixed tokens: KeepFor(window), or a third of HistoryBudget when that is smaller, so a summary leaves the history well under ThresholdFor and the next one is turns away. At least 1 token (the newest turn); an unknown window or no prefix keeps KeepFor(window).
func NeedsSummary ¶
func NeedsSummary(msgs []tui.ChatMessage, window, used int) bool
NeedsSummary reports whether a proactive summary is due: the next request (used tokens: history plus fixed prefix, as Meter.Used counts it) is over ThresholdFor, the prefix leaves the history some room, and there is history a summary can replace (summarizable). It never says yes when the summary would come back ErrNothingToSummarize for want of history, so a chat is not summarized, then "skipped", every turn.
func RenderState ¶
RenderState renders what the model must not lose to a summary, from celeste's own records (#200): the todo list, the files this session changed, and the voice rule. Deterministic: todos by ID, files sorted and de-duplicated, fixed headings, the same bytes for the same state. Empty sections are left out; no state at all renders "".
func StateMessage ¶
func StateMessage(state string) tui.ChatMessage
StateMessage carries only the state, for a server compaction whose summary is inside a signed block (ruling 9).
func SummaryMessages ¶
func SummaryMessages(summary, state string, tailStartsWithUser bool) []tui.ChatMessage
SummaryMessages builds the messages that stand in for the summarized history: a user message carrying the summary and state (not a system message, which some providers drop mid-conversation) and, when the kept tail starts with a user turn, an assistant acknowledgement so roles still alternate.
func SummaryText ¶
SummaryText is the content of the message that stands in for summarized history: the summary and the authoritative state inside <compacted-context>, then the instruction to continue.
func Threshold ¶
Threshold is the usage above which compaction runs: the window minus a reserve of max(16k, 10% of the window). Small windows keep at least half.
func ThresholdFor ¶
ThresholdFor is Threshold for requests that carry overhead fixed tokens. When the prefix crowds the window, the threshold rises so the history gets three quarters of HistoryBudget before compaction runs, but by no more than the overhead: with no prefix, and on large windows where the prefix fits, it is Threshold (200k with a 16k prefix: 180k as before).
func ToolBudget ¶
ToolBudget is the room for tool schemas on window when the system prompt is systemTokens: the window less the reply's reserve (as HistoryBudget keeps it), a quarter for history, and the system prompt. It can be negative.
Types ¶
type Candidate ¶
type Candidate struct {
ToolCallID string
Name string
Args map[string]any
Content string
Tokens int
}
Candidate is an old tool result that pass 2 may elide, oldest first.
type Edit ¶
type Edit struct {
ToolCallID string
Name string
Content string // replacement shown to the model
Original string // full body, for spilling
Tokens int // estimated tokens saved
Superseded bool
}
Edit replaces one tool result's content.
type Meter ¶
type Meter struct {
// Overhead is the system prompt and tool schemas, in estimated tokens.
Overhead int
// contains filtered or unexported fields
}
Meter tracks, for one compactor (a chat turn, an agent run, an MCP call), how big the next request will be and which trailing messages the model has not been shown yet (#234). Not safe for concurrent use: the loop calls its Compactor from Run's goroutine only.
func NewMeter ¶
NewMeter returns a meter for a compactor whose requests carry overhead tokens besides the history.
func (*Meter) Observe ¶
func (m *Meter) Observe(history []tui.ChatMessage, promptTokens int)
Observe is called first in every Compact, with the history about to be compacted and the provider's prompt tokens for the previous request (0 when it reported none). The previous request was answered when its reply, an assistant message, sits right after what it carried. A reply without a prompt count marks its request as seen but keeps the surplus the provider last reported.
func (*Meter) Sending ¶
func (m *Meter) Sending(history []tui.ChatMessage)
Sending is called last in every Compact, with the history the loop will send.
func (*Meter) Unseen ¶
func (m *Meter) Unseen(history []tui.ChatMessage) int
Unseen is how many trailing messages came after the last answered request; 0 before the first one.
func (*Meter) Used ¶
func (m *Meter) Used(history []tui.ChatMessage) int
Used estimates the next request: the history estimate plus Overhead, or plus the provider's last reported surplus over the estimate, whichever is larger. Without a prune this equals the provider's count plus what was appended after the request it measured; after a prune or summary it falls with the history.
type Options ¶
type Options struct {
// Window is the model's context window in tokens.
Window int
// Used is the best known current usage (system prompt, tools and
// history). When zero, the history estimate is used.
Used int
// Overhead is the fixed prefix (system prompt, tool schemas) inside
// Used. The prune decides on ThresholdFor(Window, Overhead), so on a
// window the prefix crowds it aims at the history's room (L2). 0 keeps
// Threshold(Window).
Overhead int
// Force prunes even below the threshold and below the minimum saving:
// the reactive path after a context-overflow error, and /context compact.
Force bool
// Unseen is how many trailing messages the model has not been shown
// yet (new tool results; see Meter). A proactive prune never edits
// them: eliding a result before its first read makes the model recall
// it, and the recall gets elided in turn (#234). Force ignores it.
Unseen int
// Score, when set, rates how likely each elision candidate is still
// needed (0..1); the least-needed are elided first. A nil or empty
// result keeps the default oldest-first order (#175).
Score func([]Candidate) map[string]float64
}
Options controls a prune.
func Acting ¶
func Acting(ctx context.Context, c *jev.Client, msgs []tui.ChatMessage, opts Options, logf func(string)) Options
Acting makes Plan ask Jev, on the caller's goroutine (the loop's, never the UI's), capped at jev.Timeout. Any error is no opinion: the rules' oldest-first order stands. Each call is counted in decide's stats as "prune".
func WithJev ¶
func WithJev(ctx context.Context, c *jev.Client, mode string, msgs []tui.ChatMessage, opts Options, logf func(string), async bool) (Options, func(Result))
WithJev applies jev_prune to a prune's options (#175, 2.0 W3). "on": Plan asks Jev for P(needed) and elides the least-needed first (Acting). "shadow": the rules decide and the returned report logs Jev's verdict (Shadow; async as Shadow describes; a synchronous report asks under ctx). Anything else, or a nil client, leaves opts alone. Call the report after the prune.
type Result ¶
Result describes a prune.
func Plan ¶
func Plan(msgs []tui.ChatMessage, opts Options) Result
Plan works out which tool results to prune; it doesn't change msgs.
Superseded results go first and need no model call: a file read again or edited later, or a read-only command run again with the same arguments. Then, if still over target, large results are elided: oldest first, or least-needed first when Options.Score rates them. The newest ~40k tokens are never touched, and a result is never removed, only replaced, so every tool call keeps its result.
func Prune ¶
func Prune(msgs []tui.ChatMessage, opts Options, store *Store) ([]tui.ChatMessage, Result)
Prune plans a prune, spills the bodies to store, and returns the history with the pruned results replaced. With a nil store nothing is pruned: a placeholder must never point at a body that wasn't saved.
type Store ¶
type Store struct {
Dir string
}
Store keeps pruned tool-result bodies on disk so recall_tool_result can return them.
func DefaultStore ¶
DefaultStore is ~/.celeste/tool-results/pruned.
type SummarizeFunc ¶
SummarizeFunc sends a system and a user prompt to a model (the small-model role) and returns its text reply.
type SummaryOptions ¶
type SummaryOptions struct {
// KeepTokens of the newest history stay verbatim; 0 means
// KeepWithin(Window, Overhead).
KeepTokens int
// Window is the model's context window; it sizes the kept tail when
// KeepTokens is 0 (#234).
Window int
// Focus is an optional instruction from /compact [focus].
Focus string
// All summarizes the whole history, keeping no tail (/handoff).
All bool
// Overhead is the fixed prefix (system prompt, tool schemas) the
// requests carry; on a crowded window it shrinks the kept tail
// (KeepWithin). 0 keeps KeepFor(Window).
Overhead int
// State is RenderState's output, appended inside the summary's
// <compacted-context> (#200). "" adds nothing.
State string
}
SummaryOptions controls a summary.
type SummaryResult ¶
type SummaryResult struct {
// Cut is how many leading messages the summary replaced.
Cut int
Summary string
TokensBefore int
TokensAfter int
HadPrevious bool
MessagesAfter int
}
SummaryResult describes a summary.
func Summarize ¶
func Summarize(ctx context.Context, msgs []tui.ChatMessage, opts SummaryOptions, summarize SummarizeFunc) ([]tui.ChatMessage, SummaryResult, error)
Summarize replaces the history before the kept tail with a structured summary written by summarize. A previous summary at the head is fed back in, so summaries accumulate instead of losing earlier work.
func (SummaryResult) Line ¶
func (r SummaryResult) Line() string
Line is a one-line description for logs and the UI.
type ToolFit ¶
type ToolFit struct {
Defs []tui.SkillDefinition // the definitions to send
Total int // the definitions offered before fitting
Dropped int // Total - len(Defs)
Reduced bool // the set was trimmed and shortened
// PinnedDropped counts tools find_tools activated that were dropped
// to fit (the oldest activations first).
PinnedDropped int
}
ToolFit is what FitTools sends.
func FitTools ¶
func FitTools(defs []tui.SkillDefinition, window, systemTokens int, pinned []string) ToolFit
FitTools returns defs as they are when they fit ToolBudget (or the window is unknown). Otherwise it sends shortened copies of the core tools, whatever they cost, then the tools the model activated this session (pinned, oldest activation first): the newest always, older ones newest first while they fit, so repeated find_tools calls cannot push the prefix past the window. Then the other tools in order while they fit, builtins before MCP tools. The order of defs is kept, so the definitions a provider caches do not move. defs is never modified.
type ToolNotices ¶
type ToolNotices struct {
// contains filtered or unexported fields
}
ToolNotices dedupes the tool fit's notice for one session (one chat, one ACP editor session, one agent run): each is told once per (kept, total, window), whatever other sessions in the process were told. The zero value is ready.