policy

package
v0.5.2 Latest Latest
Warning

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

Go to latest
Published: Apr 6, 2026 License: Apache-2.0 Imports: 7 Imported by: 0

Documentation

Overview

Package policy provides Policy implementations for the goagent memory system.

A Policy receives the full message history from storage and returns the subset that will be included in the next provider request. It has no knowledge of where messages are stored — that is the Storage layer's responsibility.

Example

Example demonstrates the NoOp policy passing all messages through unchanged.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Germanblandin1/goagent"
	"github.com/Germanblandin1/goagent/memory/policy"
)

func main() {
	p := policy.NewNoOp()
	msgs := []goagent.Message{
		goagent.UserMessage("ping"),
		goagent.AssistantMessage("pong"),
	}
	result, err := p.Apply(context.Background(), msgs)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(len(result))
}
Output:
2

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Policy

type Policy interface {
	Apply(ctx context.Context, msgs []goagent.Message) ([]goagent.Message, error)
}

Policy decides which messages from the full history are included in the next request to the provider. It is applied at read time, never at write time.

func NewFixedWindow

func NewFixedWindow(n int) Policy

NewFixedWindow returns a Policy that limits history to the most recent n groups. A "group" is an atomic exchange: a simple message (user or assistant without tools) or a linked set of assistant+tool_use + all its tool results.

Using groups instead of individual messages guarantees that the tool call invariant is never violated: a tool_result is never sent without its corresponding tool_use.

For a history without tool calls, NewFixedWindow(n) is equivalent to the last n messages — behavior is identical.

If n is zero or negative, Apply returns nil.

Example

ExampleNewFixedWindow shows that FixedWindow keeps only the n most recent messages.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Germanblandin1/goagent"
	"github.com/Germanblandin1/goagent/memory/policy"
)

func main() {
	p := policy.NewFixedWindow(2)
	msgs := []goagent.Message{
		goagent.UserMessage("one"),
		goagent.AssistantMessage("two"),
		goagent.UserMessage("three"),
		goagent.AssistantMessage("four"),
		goagent.UserMessage("five"),
	}
	result, err := p.Apply(context.Background(), msgs)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(len(result))
	fmt.Println(result[0].TextContent())
}
Output:
2
four

func NewNoOp

func NewNoOp() Policy

NewNoOp returns a Policy that passes all messages through unchanged. It is the default policy when constructing a ShortTermMemory without an explicit policy — the full history is always sent to the provider.

Example

ExampleNewNoOp shows that NewNoOp passes every message through unchanged.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Germanblandin1/goagent"
	"github.com/Germanblandin1/goagent/memory/policy"
)

func main() {
	p := policy.NewNoOp()
	msgs := []goagent.Message{
		goagent.UserMessage("a"),
		goagent.AssistantMessage("b"),
		goagent.UserMessage("c"),
	}
	result, err := p.Apply(context.Background(), msgs)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(len(result))
}
Output:
3

func NewTokenWindow

func NewTokenWindow(maxTokens int, opts ...TokenWindowOption) Policy

NewTokenWindow returns a Policy that keeps the most recent groups that fit within maxTokens estimated tokens.

Like FixedWindow, it operates on atomic groups, guaranteeing that the tool call invariant is never violated.

If the most recent group alone exceeds the budget, it is included anyway — better to send some context than none.

By default, token cost is estimated with the heuristic len(content)/4 + 4 per message. To use an exact count, pass WithTokenizer with the tokenizer of your chosen provider.

NewTokenWindow panics if maxTokens is zero or negative — this is a programming error that cannot be corrected at runtime.

Example

ExampleNewTokenWindow shows that TokenWindow keeps the most recent messages that fit within the estimated token budget. Token estimation uses len(text)/4 + 4 per message.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Germanblandin1/goagent"
	"github.com/Germanblandin1/goagent/memory/policy"
)

func main() {
	// Each message here has content of ≤3 chars, so estimated cost = 0 + 4 = 4 tokens.
	// A budget of 8 fits exactly the two most recent messages.
	p := policy.NewTokenWindow(8)
	msgs := []goagent.Message{
		goagent.UserMessage("one"),
		goagent.AssistantMessage("two"),
		goagent.UserMessage("bye"),
		goagent.AssistantMessage("ok"),
	}
	result, err := p.Apply(context.Background(), msgs)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(len(result))
	fmt.Println(result[0].TextContent())
}
Output:
2
bye
Example (WithTokenizer)

ExampleNewTokenWindow_withTokenizer shows how to plug in a custom tokenizer so that TokenWindow uses exact token counts instead of the built-in heuristic.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/Germanblandin1/goagent"
	"github.com/Germanblandin1/goagent/memory/policy"
)

func main() {
	// Pretend each message costs exactly 10 tokens (replace with a real
	// tokenizer such as tiktoken or your provider's counting endpoint).
	exact := policy.TokenizerFunc(func(msg goagent.Message) int {
		return 10
	})

	// Budget of 25 tokens: floor(25/10) = 2 messages fit.
	p := policy.NewTokenWindow(25, policy.WithTokenizer(exact))
	msgs := []goagent.Message{
		goagent.UserMessage("first"),
		goagent.AssistantMessage("second"),
		goagent.UserMessage("third"),
	}
	result, err := p.Apply(context.Background(), msgs)
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(len(result))
	fmt.Println(result[0].TextContent())
}
Output:
2
second

type TokenWindowOption

type TokenWindowOption func(*tokenWindowPolicy)

TokenWindowOption configures a TokenWindow policy.

func WithTokenizer

func WithTokenizer(fn TokenizerFunc) TokenWindowOption

WithTokenizer sets the TokenizerFunc used to estimate message cost. Use this to plug in the exact tokenizer of your provider (e.g. tiktoken for OpenAI-compatible models, or Anthropic's token-counting endpoint).

If WithTokenizer is not provided, TokenWindow uses the built-in heuristic:

  • Text blocks: len(text)/4 tokens
  • Image blocks (JPEG/PNG/GIF): ceil(w/64)×ceil(h/64)×170 tokens, derived from the image dimensions decoded at runtime. Falls back to 2500 tokens when the format is unsupported (e.g. WebP, which requires the external dependency golang.org/x/image/webp and is not decoded by the stdlib). The fallback is conservative on purpose: overestimating trims the window slightly, while underestimating can cause the provider to reject the request outright.
  • Document blocks: len(data)/1500 tokens
  • Per-message overhead: 4 tokens

type TokenizerFunc

type TokenizerFunc func(msg goagent.Message) int

TokenizerFunc counts the tokens consumed by a single message. It is used by TokenWindow to decide how many messages fit within the budget.

The function must be pure and safe for concurrent use. Returning a value ≤ 0 is treated as zero cost for that message.

Jump to

Keyboard shortcuts

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