mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-12 08:14:06 +00:00
The v3 pipeline compacts session history mid-loop (prune_stage + final-request guard) but only mutates the run's message buffer, never the session store. Each turn reloads full history and re-compacts from scratch: message_tokens climb 129k->156k across turns while every turn compacts back down to ~60k. The lossy compaction differs per run, degrading the agent. The same missing persistence stalls episodic memory: the cumulative compaction count never advances, so the episodic worker's idempotency key (sessionKey:count) is pinned and every cycle after the first is skipped. Observed on live traffic: 8 run.completed since deploy, 0 new episodic. Fixes, all reusing existing machinery (no new store methods, no migrations): - Bug A: emitSessionCompleted reads cumulative GetCompactionCount (matching the legacy v2 path) instead of the per-run counter that resets to 0. - Bug B/anti-loop: finalize passes state.Prune.MidLoopCompacted into maybeSummarize; under pressure it lowers the trigger to a unit-aligned threshold (compactionInputCap - overhead, same MaxRequestShare the guard uses) so the compaction is PERSISTED via the existing TruncateHistory + IncrementCompaction path. Defensive floor prevents over-compaction on pathological config; tool-result-only bloat still skips (history-only). - Bug C: SourceID embeds the count (sessionKey:count) so the eventbus dedup key advances per compaction cycle instead of swallowing rapid same-session turns within the 5m TTL. Tests: episodic compaction, maybe_summarize pressure, request budget. go build (PG + sqliteonly), go vet, go test -race all green.
42 lines
1.1 KiB
Go
42 lines
1.1 KiB
Go
package agent
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/providers"
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
)
|
|
|
|
// Agent is the core abstraction for an AI agent execution loop.
|
|
// Implemented by *Loop; extracted as an interface for testability and composability.
|
|
type Agent interface {
|
|
ID() string
|
|
UUID() uuid.UUID
|
|
OtherConfig() json.RawMessage
|
|
Run(ctx context.Context, req RunRequest) (*RunResult, error)
|
|
IsRunning() bool
|
|
Model() string
|
|
ProviderName() string
|
|
Provider() providers.Provider
|
|
}
|
|
|
|
// BudgetedAgent exposes the configured agent-only request budget without adding
|
|
// model/provider authority or expanding the broad Agent test interface.
|
|
type BudgetedAgent interface {
|
|
ContextWindow() int
|
|
MaxTokens() int
|
|
}
|
|
|
|
// WithAgentBudget propagates an agent's configured budget to nested model calls.
|
|
func WithAgentBudget(ctx context.Context, a Agent) context.Context {
|
|
budgeted, ok := a.(BudgetedAgent)
|
|
if !ok {
|
|
return ctx
|
|
}
|
|
ctx = store.WithAgentContextWindow(ctx, budgeted.ContextWindow())
|
|
return store.WithAgentMaxTokens(ctx, budgeted.MaxTokens())
|
|
}
|