actx

package
v1.4.0 Latest Latest
Warning

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

Go to latest
Published: Oct 31, 2026 License: MIT Imports: 16 Imported by: 0

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

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

View Source
const ClearedPlaceholder = "[tool result cleared to save context]"

ClearedPlaceholder replaces the content of cleared tool results.

View Source
const DefaultSummaryPrompt = `` /* 620-byte string literal not displayed */

DefaultSummaryPrompt instructs the model how to summarize a conversation.

Variables

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

func EstimateTokens(s string) int

EstimateTokens estimates the tokens in s.

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

func Transcript(msgs []*model.Message, maxResultBytes int) string

Transcript renders messages as plain text. Tool results longer than maxResultBytes are truncated.

func Turns

func Turns(msgs []*model.Message) [][]*model.Message

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

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

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

type CompactResult struct {
	Messages []*model.Message
	Before   int
	After    int
	Summary  string
}

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

func NewManager(cfg Config) *Manager

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

func (m *Manager) AddProvider(p Provider)

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) Count

func (m *Manager) Count(system string, msgs []*model.Message, tools []*model.ToolSpec) int

Count estimates the input size of a request.

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.

func (*Manager) Prepare

func (m *Manager) Prepare(ctx context.Context, instructions string, in *Input) (*Prepared, error)

Prepare collects sections from all providers. Provider errors are joined and returned along with the sections that succeeded.

func (*Manager) Window

func (m *Manager) Window() Window

Window returns the configured window.

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

type Provider interface {
	Sections(ctx context.Context, in *Input) ([]Section, error)
}

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.

func Static

func Static(name, content string, placement Placement) Provider

Static returns a provider that always contributes the given section.

type ProviderFunc

type ProviderFunc func(ctx context.Context, in *Input) ([]Section, error)

ProviderFunc adapts a function to Provider.

func (ProviderFunc) Sections

func (f ProviderFunc) Sections(ctx context.Context, in *Input) ([]Section, error)

Sections calls f.

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.

func Chain

func Chain(strategies ...Strategy) Strategy

Chain applies strategies in order until the conversation fits the target. Cheaper, lossless strategies should come first.

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

type TokenCounter interface {
	CountText(s string) int
	CountMessages(msgs []*model.Message) int
}

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

func (w Window) ShouldCompact(tokens int) bool

ShouldCompact reports whether input of the given size should be compacted.

func (Window) Target

func (w Window) Target() int

Target returns the input size compaction aims for.

func (Window) Usable

func (w Window) Usable() int

Usable returns the tokens available for input.

Jump to

Keyboard shortcuts

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