Documentation
¶
Overview ¶
Package actx implements context engineering for agents: assembling the system prompt and a reminder for each new input from providers, tracking the context window and compacting conversations that grow too large. Providers run once per run, before it starts, and the reminder is attached to that run's user input.
Compaction edits earlier history, which invalidates Anthropic thinking replayed after the edit on models that check the prefix of thinking blocks, such as Claude Opus 5.5. The anthropic provider asks the API to drop such blocks, so requests succeed but the model thinks again.
Index ¶
- Constants
- Variables
- func EstimateTokens(s string) int
- func Middleware(m *Manager, opts ...MiddlewareOption) loop.Middleware
- func Transcript(msgs []*model.Message, maxResultBytes int) string
- func Turns(msgs []*model.Message) [][]*model.Message
- type ClearToolArguments
- type ClearToolResults
- type CompactInput
- type CompactOutput
- type CompactResult
- type Config
- type EstimateCounter
- type Input
- type InstructionConfig
- type Manager
- func (m *Manager) AddProvider(p Provider)
- func (m *Manager) Compact(ctx context.Context, system string, msgs []*model.Message, ...) (*CompactResult, error)
- func (m *Manager) Count(system string, msgs []*model.Message, tools []*model.ToolSpec) int
- func (m *Manager) NeedsCompaction(system string, msgs []*model.Message, tools []*model.ToolSpec) (bool, int)
- func (m *Manager) Observe(system string, msgs []*model.Message, tools []*model.ToolSpec, ...)
- func (m *Manager) Prepare(ctx context.Context, instructions string, in *Input) (*Prepared, error)
- func (m *Manager) Window() Window
- type MiddlewareOption
- type Placement
- type Prepared
- type Provider
- type ProviderFunc
- type Section
- type Strategy
- type StrategyFunc
- type Summarize
- type TokenCounter
- type Truncate
- type Window
Constants ¶
const ( // DefaultInstructionMaxBytes is the default total size of instruction // files, matching the project_doc_max_bytes default of Codex. DefaultInstructionMaxBytes = 32 << 10 // DefaultImportDepth is the default number of nested @path import hops, // matching Claude Code. DefaultImportDepth = 4 )
const ClearedArguments = `{"cleared":"[tool call arguments cleared to save context]"}`
ClearedArguments replaces the arguments of cleared tool calls. It is a JSON object, so every provider accepts it as tool input.
const ClearedPlaceholder = "[tool result cleared to save context]"
ClearedPlaceholder replaces the content of cleared tool results.
const DefaultSummaryPrompt = `` /* 620-byte string literal not displayed */
DefaultSummaryPrompt instructs the model how to summarize a conversation.
Variables ¶
var DefaultWindow = Window{Limit: 200_000, ReserveOutput: 16_000, Threshold: 0.8, TargetRatio: 0.5}
DefaultWindow suits models with a 200K-token context window.
Functions ¶
func Middleware ¶
func Middleware(m *Manager, opts ...MiddlewareOption) loop.Middleware
Middleware returns runtime middleware that engineers the context of each run with m: it adds the providers' sections to the system prompt and the input, compacts the conversation when it outgrows the window and calibrates token estimates against reported usage. A nil m uses the defaults of NewManager.
Provider and compaction failures are logged and do not stop the run. A compaction that does not shrink the conversation is discarded without an EventCompacted. When the conversation still exceeds the usable window after compaction, a warning is logged and the request is sent anyway; the provider may reject it.
Inside a run traced by middleware.Tracing, an automatic compaction gets a "compact_context" span, and the run's later model spans carry gen_ai.conversation.compacted.
func Transcript ¶
Transcript renders messages as plain text. Tool results longer than maxResultBytes are truncated.
func Turns ¶
Turns splits a conversation into turns. A turn starts with a user message and contains the assistant and tool messages that follow it, so tool calls and their results always stay in the same turn.
A user message that holds nothing but <system-reminder> blocks (see model.WrapReminder) does not start a turn. The application, not the user, adds such messages to continue the current task, for example when a Stop or SubagentStop hook blocks the model from finishing, so they stay in the turn of the request they continue and compaction keeps that request.
Types ¶
type ClearToolArguments ¶
type ClearToolArguments struct {
// KeepRecent is the number of recent assistant messages with tool calls
// kept intact. Defaults to 3.
KeepRecent int
// MinBytes skips arguments shorter than this. Defaults to 1024.
MinBytes int
}
ClearToolArguments replaces the large arguments of older, already answered tool calls with ClearedArguments, keeping the calls of the most recent KeepRecent assistant messages intact. It shrinks long agentic runs whose size comes from what the model wrote, such as file contents passed to a write tool, rather than from tool results.
Calls without a result are never changed, so tool calls and results stay paired. Assistant messages with any signed part, such as a signed tool call (Gemini signs the first call of a parallel step) or signed or redacted reasoning (Anthropic), are left whole, so no call in a signed step is rewritten.
func (ClearToolArguments) Compact ¶
func (s ClearToolArguments) Compact(_ context.Context, in *CompactInput) (*CompactOutput, error)
Compact implements Strategy.
type ClearToolResults ¶
type ClearToolResults struct {
// KeepRecent is the number of recent tool result messages kept intact.
// All results of one batch of parallel calls share a message. Defaults
// to 3.
KeepRecent int
// MinBytes skips results shorter than this. Defaults to 256.
MinBytes int
}
ClearToolResults replaces the content of older tool results with a placeholder, keeping the most recent KeepRecent tool result messages intact. It is lossless with respect to conversation structure and usually the cheapest strategy.
Results the model has not seen yet, those after the last assistant message, are never cleared, so a large batch of parallel calls is read by the model before any of it is cleared. The trade-off is that no strategy in the default chain shrinks such a batch: Truncate also keeps the last step of a turn. If one unseen batch alone exceeds the window, compaction cannot fit the conversation, the middleware logs a warning and the request may fail. Bound tool output, for example with tool.TruncateOutput, when tools can return results that large.
func (ClearToolResults) Compact ¶
func (s ClearToolResults) Compact(_ context.Context, in *CompactInput) (*CompactOutput, error)
Compact implements Strategy.
type CompactInput ¶
type CompactInput struct {
System string
Messages []*model.Message
// Target is the desired size of the messages in tokens.
Target int
Counter TokenCounter
}
CompactInput is the input to a compaction strategy.
type CompactOutput ¶
type CompactOutput struct {
Messages []*model.Message
// Summary is set by summarizing strategies.
Summary string
}
CompactOutput is the result of a compaction strategy.
type CompactResult ¶
CompactResult describes a compaction.
type Config ¶
type Config struct {
Window Window
Counter TokenCounter
// Strategy compacts the conversation. Defaults to
// Chain(ClearToolResults{}, ClearToolArguments{}, Truncate{}).
Strategy Strategy
Providers []Provider
// DisableAutoCompact turns off automatic compaction; Compact can still be
// called explicitly.
DisableAutoCompact bool
}
Config configures a Manager.
type EstimateCounter ¶
type EstimateCounter struct{}
EstimateCounter is a dependency-free heuristic counter. ASCII text counts as one token per four bytes and every other rune as one token, which is a reasonable upper estimate for CJK text. Use it when exact counts are not required; Manager calibrates it against reported usage.
func (EstimateCounter) CountMessages ¶
func (EstimateCounter) CountMessages(msgs []*model.Message) int
CountMessages estimates the tokens in msgs, including per-message framing.
func (EstimateCounter) CountText ¶
func (EstimateCounter) CountText(s string) int
CountText estimates the tokens in s.
type Input ¶
type Input struct {
SessionID string
Agent string
// Messages is the conversation history before the new input.
Messages []*model.Message
// Query is the text of the new user input.
Query string
}
Input describes the run for which context is assembled.
type InstructionConfig ¶
type InstructionConfig struct {
// Dir is the working directory instructions are loaded for. Defaults to
// ".".
Dir string
// Names lists the instruction file names checked in each directory, in
// order of precedence; the first one that exists as a file is loaded
// and the rest are ignored. Names may contain slash-separated
// subdirectories, such as ".claude/CLAUDE.md", but must stay inside the
// directory. Defaults to AGENTS.override.md, AGENTS.md, CLAUDE.md.
Names []string
// LocalNames lists files loaded in each directory after the file chosen
// from Names, all that exist, for example CLAUDE.local.md. None by
// default.
LocalNames []string
// RootMarkers are the entries, of any type, that mark the project root.
// Directories are searched from the nearest ancestor of Dir containing
// a marker down to Dir. When no ancestor has a marker, or the list is
// empty but not nil, only Dir is searched. Defaults to .git.
RootMarkers []string
// UserFiles are personal instruction files, such as
// ~/.codex/AGENTS.md, loaded before the project's, in order. Missing
// files are skipped. A leading ~ is the user's home directory. No file
// outside the project is read unless listed here or in ImportDirs.
UserFiles []string
// ImportDirs are directories outside the project root from which any
// instruction file may import. A leading ~ is the user's home
// directory.
ImportDirs []string
// MaxBytes bounds the total size of all files loaded; the content past
// it is dropped with a note. Defaults to DefaultInstructionMaxBytes; a
// negative value disables the limit.
MaxBytes int
// ImportDepth bounds how many hops of @path imports are followed.
// Defaults to DefaultImportDepth; a negative value disables imports.
ImportDepth int
// Priority orders the sections among those of other providers.
Priority int
}
InstructionConfig configures InstructionFiles.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager assembles context and compacts conversations.
A nil *Manager behaves like NewManager(Config{}).
func NewManager ¶
NewManager returns a manager.
The default strategy never clears tool results the model has not seen yet and always keeps the latest step of a turn, so a single batch of unseen tool results larger than the window cannot be compacted; see ClearToolResults.
func (*Manager) AddProvider ¶
AddProvider registers a provider. It is not safe to call concurrently with Prepare.
func (*Manager) Compact ¶
func (m *Manager) Compact(ctx context.Context, system string, msgs []*model.Message, tools []*model.ToolSpec) (*CompactResult, error)
Compact shrinks the conversation towards the window target. The system prompt and tools are counted against the target.
func (*Manager) NeedsCompaction ¶
func (m *Manager) NeedsCompaction(system string, msgs []*model.Message, tools []*model.ToolSpec) (bool, int)
NeedsCompaction reports whether automatic compaction should run, and the estimated input size.
func (*Manager) Observe ¶
func (m *Manager) Observe(system string, msgs []*model.Message, tools []*model.ToolSpec, usage model.Usage)
Observe calibrates estimates against the prompt size reported by the provider for a request. The first observation sets the calibration; later ones are averaged with it, halving the weight of older requests each time.
type MiddlewareOption ¶
type MiddlewareOption func(*middleware)
MiddlewareOption configures Middleware.
func WithCompactGuard ¶
func WithCompactGuard(guard func(ctx context.Context, run *loop.Run, reason string) bool) MiddlewareOption
WithCompactGuard calls guard before each automatic compaction; returning false skips it. hook.PreCompactGuard adapts PreCompact hooks.
func WithCompactObserver ¶
func WithCompactObserver(observe func(ctx context.Context, run *loop.Run, reason string, c *loop.Compaction)) MiddlewareOption
WithCompactObserver calls observe after each automatic compaction that replaced the conversation, with the reason passed to the guard ("auto") and the compaction. hook.PostCompactObserver adapts PostCompact hooks.
type Placement ¶
type Placement int
Placement says where a section is inserted.
const ( // PlaceSystem appends the section to the system prompt. Use it for stable // content so the prompt prefix stays cacheable. PlaceSystem Placement = iota // PlaceReminder attaches the section to the user input of the current // run inside a <system-reminder> block. Use it for content that varies // per request, such as recalled memories. PlaceReminder )
type Prepared ¶
type Prepared struct {
// System is the base instructions followed by system sections.
System string
// Reminder is the combined reminder sections, already wrapped in a
// <system-reminder> block, or empty.
Reminder string
}
Prepared is the context assembled for a run.
type Provider ¶
Provider contributes sections to the model context.
func InstructionFiles ¶
func InstructionFiles(cfg InstructionConfig) Provider
InstructionFiles returns a provider that adds project instruction files, such as AGENTS.md and CLAUDE.md, to the system prompt, the way Codex and Claude Code load them.
The project root is the nearest ancestor of cfg.Dir containing one of cfg.RootMarkers. In every directory from the root down to cfg.Dir, the first of cfg.Names that exists is loaded, followed by any of cfg.LocalNames. cfg.UserFiles come first, so instructions closer to cfg.Dir are read last. Each file becomes a PlaceSystem section, named and labeled with its path: relative to the root for project files, as given for user files.
A file may import others with @path outside Markdown code spans and fenced code blocks, for example "See @docs/style.md". Paths are relative to the importing file, absolute, or start with ~ for the home directory; a backslash escapes a space, and quoted paths are not imported. Trailing punctuation is dropped when the file with it does not exist. Imported files follow the file that imports them, up to cfg.ImportDepth hops. A file is loaded at most once, so import cycles end. Missing imports are skipped.
Instruction files are untrusted repository content. Project files and their imports are read only inside the project root, or cfg.Dir when there is none; a user file's imports only inside its directory; both may also import from cfg.ImportDirs. Paths that leave these directories, through .. or symbolic links, are skipped, as are absolute symbolic links.
Files are read again on each run, so edits take effect on the next run; changed content also changes the system prompt prefix. Failing to read a file that exists returns an error, and the run proceeds without instruction files.
type ProviderFunc ¶
ProviderFunc adapts a function to Provider.
type Section ¶
type Section struct {
Name string
Content string
Placement Placement
// Priority orders sections; lower values come first. Sections with equal
// priority keep provider order.
Priority int
}
Section is a piece of context contributed by a Provider.
type Strategy ¶
type Strategy interface {
Compact(ctx context.Context, in *CompactInput) (*CompactOutput, error)
}
Strategy reduces the size of a conversation.
Strategies must not modify the input messages; they return new slices and clone any message they change.
type StrategyFunc ¶
type StrategyFunc func(ctx context.Context, in *CompactInput) (*CompactOutput, error)
StrategyFunc adapts a function to Strategy.
func (StrategyFunc) Compact ¶
func (f StrategyFunc) Compact(ctx context.Context, in *CompactInput) (*CompactOutput, error)
Compact calls f.
type Summarize ¶
type Summarize struct {
Model model.Model
// KeepRecent defaults to 2.
KeepRecent int
// Prompt defaults to DefaultSummaryPrompt.
Prompt string
// MaxTokens bounds the summary. Defaults to 4096.
MaxTokens int
// MaxResultBytes truncates each tool result in the transcript. Defaults
// to 2000.
MaxResultBytes int
}
Summarize replaces older turns with a model-written summary, keeping the most recent KeepRecent turns verbatim. System messages are kept verbatim and are not summarized.
func (Summarize) Compact ¶
func (s Summarize) Compact(ctx context.Context, in *CompactInput) (*CompactOutput, error)
Compact implements Strategy.
type TokenCounter ¶
TokenCounter estimates the token size of model input.
type Truncate ¶
type Truncate struct {
// KeepRecent defaults to 1.
KeepRecent int
}
Truncate drops the oldest turns until the conversation fits the target. The most recent KeepRecent turns are not dropped as a whole, but if they still do not fit, it drops the oldest steps inside them, where a step is an assistant message together with the tool results that answer it. The user message that starts each turn and the last step of each turn are kept, and a tool call is never separated from its result.
In the turn still in progress, the last one when it ends with tool results, the first step with signed or redacted reasoning is kept too: with Anthropic extended thinking the current assistant turn must start with its thinking block. If the conversation still does not fit, that step's tool results are cleared instead.
System messages are never dropped.
func (Truncate) Compact ¶
func (s Truncate) Compact(_ context.Context, in *CompactInput) (*CompactOutput, error)
Compact implements Strategy.
type Window ¶
type Window struct {
// Limit is the model's context window in tokens.
Limit int
// ReserveOutput is kept free for the response.
ReserveOutput int
// Threshold is the fraction of the usable window at which automatic
// compaction starts. Defaults to 0.8.
Threshold float64
// TargetRatio is the fraction of the usable window that compaction aims
// for. Defaults to 0.5.
TargetRatio float64
}
Window describes the model's context window and when to compact.
func (Window) ShouldCompact ¶
ShouldCompact reports whether input of the given size should be compacted.