Help us shape the future of Go! Take the 2026 Go Developer Survey

compact

package
v2.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 9, 2026 License: MIT Imports: 18 Imported by: 0

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

View Source
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

View Source
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).

View Source
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

func HandoffText(summary string) string

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

func HistoryBudget(window, overhead int) int

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

func KeepFor(window int) int

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

func KeepWithin(window, overhead int) int

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

func RenderState(todos []Todo, files []string, voiceRule string) string

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

func SummaryText(summary, state string) string

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

func Threshold(window int) int

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

func ThresholdFor(window, overhead int) int

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

func ToolBudget(window, systemTokens int) int

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

func NewMeter(overhead int) *Meter

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

type Result struct {
	Edits       []Edit
	SavedTokens int
	Superseded  int
	Elided      int
}

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.

func (Result) Pruned

func (r Result) Pruned() bool

Pruned reports whether the prune changed anything.

func (Result) Summary

func (r Result) Summary() string

Summary is a one-line description for logs and the UI.

type Store

type Store struct {
	Dir string
}

Store keeps pruned tool-result bodies on disk so recall_tool_result can return them.

func DefaultStore

func DefaultStore() (*Store, error)

DefaultStore is ~/.celeste/tool-results/pruned.

func (*Store) Commit

func (s *Store) Commit(edits []Edit) []Edit

Commit spills every edit's original body and returns the edits whose body was saved. An edit whose spill fails is dropped, so nothing is ever replaced by a pointer to a file that doesn't exist.

func (*Store) Load

func (s *Store) Load(id string) (string, error)

Load reads a pruned body back.

func (*Store) Save

func (s *Store) Save(id, body string) error

Save writes a pruned body. The file is private: tool output can hold secrets.

type SummarizeFunc

type SummarizeFunc func(ctx context.Context, system, user string) (string, error)

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 Todo

type Todo struct {
	ID     int
	Title  string
	Status string // pending, in_progress, done
}

Todo is one todo-list item as the state block shows it.

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.

func (*ToolNotices) Notice

func (n *ToolNotices) Notice(fit ToolFit, window int) string

Notice is the one-time notice for a reduced fit on window: "" when fit was not reduced, or once this session was told about the same fit. Like the persona guard's, the caller shows it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL