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 ¶
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 ¶
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.