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

contextkit

package
v0.3.0 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2026 License: Apache-2.0 Imports: 3 Imported by: 0

README

ContextKit

contextkit is a standalone Go module for context compression. It belongs in the broader goagents workspace, but it does not import goagent. Applications map their runtime messages into contextkit.Message, compress them, and decide how to use the returned projection.

Install

go get github.com/eruca/goagents@latest

Then import github.com/eruca/goagents/contextkit.

Compression Profile

The environment variable is a single deep-compression flag:

CONTEXT_DEEP_COMPRESSION=1

Unset or any value other than 1, true, yes, or on enables levels 1-3. Setting CONTEXT_DEEP_COMPRESSION=1 enables levels 1-5.

CONTEXT_DEEP_COMPRESSION names the behavior directly: the normal path still compresses context, while the flag opts into the deeper, more invasive layers.

Five Levels

standard profile:

  1. Tool observation projection: tool messages become structured observations containing tool name, status, result reference, and bounded result preview.
  2. Message window pruning: system messages and the newest whole turns that fit the budget are retained; everything older becomes one system summary message.
  3. Projection: the returned message slice is a model-facing view, not a mutation of the caller's original session.

deep profile adds:

  1. Reversible collapse: collapsed messages are recorded with a stable collapse ID, original IDs, original messages, and summary placeholder.
  2. Auto compact: when a Summarizer is configured, deep mode uses it to build a stronger compact summary and places that summary in the model projection.

No LLM dependency is required. Hosts can provide a deterministic summarizer, an LLM summarizer, or no summarizer. Without a summarizer, deep mode still records the collapse with a deterministic placeholder summary.

Window Compressor

compressor := window.New(window.Config{
	Budget:       contextkit.Budget{MaxChars: 4000},
	MinRecent:    8,
	MaxToolChars: 1000,
})

result, err := compressor.Compress(ctx, contextkit.Request{
	Messages: messages,
})
if err != nil {
	panic(err)
}

modelMessages := result.Messages

The window is chosen from the newest message backwards. It always holds the last MinRecent messages (default 1), starts at a user message so a tool reply never loses its call, and extends over earlier whole turns while system messages plus the kept turns fit MaxChars. The summary message is not counted. Without a budget nothing is dropped. When the newest turn alone is too large it is kept anyway and Result.OverBudget is set.

Compression is incremental: keep result.Messages as the session transcript and append new messages to it. The next Compress recognizes its own summary, summarizes only the newly dropped messages together with it, and carries the total dropped count forward. Passing the full raw history each time works too, but re-summarizes everything.

If Profile is omitted from window.Config, the compressor reads CONTEXT_DEEP_COMPRESSION.

Deep Summarization

Deep mode can use a host-owned summarizer:

type summarizer struct{}

func (summarizer) Summarize(ctx context.Context, req contextkit.SummarizeRequest) (*contextkit.SummarizeResult, error) {
	return &contextkit.SummarizeResult{
		Summary: "Primary request: ...\nCurrent state: ...\nPending tasks: ...",
	}, nil
}

compressor := window.New(window.Config{
	Profile:    contextkit.ProfileDeep,
	MinRecent:  8,
	Summarizer: summarizer{},
})

The summarizer receives the previous summary (Previous, empty on the first compression), the newly collapsed messages, recent messages, budget, and metadata. It should fold the collapsed messages into the previous summary. The returned summary replaces the generic placeholder in the model-facing projection:

summary of earlier context:
Primary request: ...
Current state: ...
Pending tasks: ...

Result.Collapses still keeps the original collapsed messages, so hosts can recover or inspect the pre-compact history.

Restore A Projection

Deep collapse records are reversible:

restored := contextkit.RestoreProjection(result.Messages, result.Collapses)

This expands collapse summary messages back to their original messages when the projection contains contextkit.collapse_id metadata. Pass every collapse recorded across incremental compressions: each collapse keeps the previous summary as one of its originals, so the expansion recurses back to the full history.

Tool Result Budget

budgeted := toolbudget.Apply(rawToolOutput, toolbudget.Config{MaxChars: 1000})
fmt.Println(budgeted.Content)

Use this at tool boundaries when a host wants explicit level-1 control before messages are assembled.

Tool Observation Projection

toolprojection is the recommended Level 1 primitive for agent traces:

projected := toolprojection.Project(contextkit.Message{
	Role:       contextkit.RoleTool,
	ToolName:   "ocr_document",
	ToolCallID: "call-1",
	Status:     "success",
	Ref:        "ocr:run-1",
	Content:    fullToolResult,
}, toolprojection.Config{MaxResultChars: 1000})

The projected content is shaped for model context:

tool=ocr_document
status=success
tool_call_id=call-1
ref=ocr:run-1
result=...

This removes noisy tool reasoning and raw payloads from the model-facing view while preserving the useful observation.

Use With goagent

Keep the dependency direction at the application boundary:

application
  imports github.com/eruca/goagents/goagent
  imports github.com/eruca/goagents/contextkit
  maps goagent messages into contextkit messages
  uses contextkit output as the model-facing projection

github.com/eruca/goagents/goagent does not import github.com/eruca/goagents/contextkit
github.com/eruca/goagents/contextkit does not import github.com/eruca/goagents/goagent

Until goagent has a native context compression port, use contextkit in host code around memory or model-request preparation. A future goagent stage can call the same compressor before ThinkStage without moving the algorithms into core.

Typical mapping:

func toContextKit(messages []agentcore.Message) []contextkit.Message {
	out := make([]contextkit.Message, 0, len(messages))
	for _, msg := range messages {
		out = append(out, contextkit.Message{
			Role:    contextkit.Role(msg.Role),
			Content: msg.Content,
		})
	}
	return out
}

For large tools such as OCR or retrieval, pair contextkit with bounded tool results:

  • return a preview in tools.Result.ForLLM
  • keep full output in tools.Result.ForUser, object storage, or a retrieval index
  • expose bounded read/search tools for follow-up access

Verify

go test ./...

Documentation

Index

Constants

View Source
const EnvDeepCompression = "CONTEXT_DEEP_COMPRESSION"

Variables

This section is empty.

Functions

This section is empty.

Types

type Budget

type Budget struct {
	MaxChars int `json:"max_chars"`
}

type Collapse

type Collapse struct {
	ID          string    `json:"id"`
	StartIndex  int       `json:"start_index"`
	EndIndex    int       `json:"end_index"`
	Summary     Message   `json:"summary"`
	OriginalIDs []string  `json:"original_ids"`
	Originals   []Message `json:"originals,omitempty"`
}

type Compact

type Compact struct {
	Summary  string         `json:"summary"`
	Metadata map[string]any `json:"metadata,omitempty"`
}

type Compressor

type Compressor interface {
	Compress(ctx context.Context, req Request) (*Result, error)
}

type Level

type Level int
const (
	LevelToolResultBudget   Level = 1
	LevelWindowPruning      Level = 2
	LevelProjection         Level = 3
	LevelReversibleCollapse Level = 4
	LevelAutoCompact        Level = 5
)

func LevelsForProfile

func LevelsForProfile(profile Profile) []Level

type Message

type Message struct {
	ID         string         `json:"id,omitempty"`
	Role       Role           `json:"role"`
	Content    string         `json:"content"`
	ToolName   string         `json:"tool_name,omitempty"`
	ToolCallID string         `json:"tool_call_id,omitempty"`
	Status     string         `json:"status,omitempty"`
	Ref        string         `json:"ref,omitempty"`
	Metadata   map[string]any `json:"metadata,omitempty"`
}

func RestoreProjection

func RestoreProjection(messages []Message, collapses []Collapse) []Message

RestoreProjection replaces each collapse summary in messages with the messages it collapsed, recursively, so chained incremental collapses expand back to the full history.

func (Message) CharLen

func (m Message) CharLen() int

type Profile

type Profile string
const (
	ProfileStandard Profile = "standard"
	ProfileDeep     Profile = "deep"
)

func ProfileFromEnv

func ProfileFromEnv() Profile

func ProfileFromLookup

func ProfileFromLookup(lookup func(string) (string, bool)) Profile

type Request

type Request struct {
	Messages []Message      `json:"messages"`
	Budget   Budget         `json:"budget"`
	Metadata map[string]any `json:"metadata,omitempty"`
}

type Result

type Result struct {
	Messages []Message `json:"messages"`
	Profile  Profile   `json:"profile"`
	Levels   []Level   `json:"levels"`
	Summary  string    `json:"summary,omitempty"`
	Dropped  int       `json:"dropped"`
	// OverBudget reports that system messages and the kept conversation
	// still exceed the budget, because the newest turn alone does.
	OverBudget  bool           `json:"over_budget,omitempty"`
	Collapses   []Collapse     `json:"collapses,omitempty"`
	AutoCompact *Compact       `json:"auto_compact,omitempty"`
	Metadata    map[string]any `json:"metadata,omitempty"`
}

type Role

type Role string
const (
	RoleSystem    Role = "system"
	RoleUser      Role = "user"
	RoleAssistant Role = "assistant"
	RoleTool      Role = "tool"
)

type SummarizeRequest

type SummarizeRequest struct {
	// Previous is the summary an earlier Compress produced; fold Collapsed
	// into it rather than summarizing from scratch.
	Previous  string         `json:"previous,omitempty"`
	Collapsed []Message      `json:"collapsed"`
	Recent    []Message      `json:"recent"`
	Budget    Budget         `json:"budget"`
	Metadata  map[string]any `json:"metadata,omitempty"`
}

type SummarizeResult

type SummarizeResult struct {
	Summary  string         `json:"summary"`
	Metadata map[string]any `json:"metadata,omitempty"`
}

type Summarizer

type Summarizer interface {
	Summarize(ctx context.Context, req SummarizeRequest) (*SummarizeResult, error)
}

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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