Files
picoclaw/pkg/agent/context_manager.go
T
Liu Yuan f7e768152e feat(agent): /clear now clears seahorse DB in addition to JSONL
- Add Clear(ctx, sessionKey) to ContextManager interface
- Implement Clear for legacy (JSONL) and seahorse (DB + JSONL)
- Add Engine.ClearSession + Store.ClearConversation
- Fix FTS5 DELETE trigger syntax in schema (was using wrong
  external-content FTS5 syntax; now uses standard DELETE FROM)
- Fix ClearSession to skip sessions never ingested (was creating
  blank conversations record via GetOrCreateConversation)
- Simplify summary_parents DELETE into single OR statement
- Add TestStoreClearConversation unit test
2026-04-13 11:04:45 +08:00

95 lines
3.1 KiB
Go

package agent
import (
"context"
"encoding/json"
"fmt"
"sync"
"github.com/sipeed/picoclaw/pkg/providers"
)
// ContextManager manages conversation context via a pluggable strategy.
// Exactly ONE ContextManager is active per AgentLoop, selected by config.
// The default ("legacy") preserves current summarization behavior.
type ContextManager interface {
// Assemble builds budget-aware context from the ContextManager's own storage.
// Called before BuildMessages. Returns assembled messages ready for LLM.
Assemble(ctx context.Context, req *AssembleRequest) (*AssembleResponse, error)
// Compact compresses conversation history.
// Called after turn completes (may be async internally) and on context overflow (sync).
Compact(ctx context.Context, req *CompactRequest) error
// Ingest records a message into the ContextManager's own storage.
// Called after each message is persisted to session JSONL.
Ingest(ctx context.Context, req *IngestRequest) error
// Clear removes all stored context for a session (messages, summaries, etc.).
// Called when the user issues /clear or /reset.
Clear(ctx context.Context, sessionKey string) error
}
// AssembleRequest is the input to Assemble.
type AssembleRequest struct {
SessionKey string // session identifier
Budget int // context window in tokens
MaxTokens int // max response tokens
}
// AssembleResponse is the output of Assemble.
type AssembleResponse struct {
History []providers.Message // assembled conversation history for BuildMessages
Summary string // conversation summary embedded into system prompt by BuildMessages
}
// CompactRequest is the input to Compact.
type CompactRequest struct {
SessionKey string // session identifier
Reason ContextCompressReason // proactive_budget | llm_retry | summarize
Budget int // context window budget (used for retry aggressive compaction)
}
// IngestRequest is the input to Ingest.
type IngestRequest struct {
SessionKey string // session identifier
Message providers.Message // the message just persisted
}
// ContextManagerFactory constructs a ContextManager from config.
// al provides access to the AgentLoop's runtime resources (provider, model, workspace, etc.)
// cfg is the raw JSON configuration from config.json (may be nil).
type ContextManagerFactory func(cfg json.RawMessage, al *AgentLoop) (ContextManager, error)
var (
cmRegistryMu sync.RWMutex
cmRegistry = map[string]ContextManagerFactory{}
)
// RegisterContextManager registers a named ContextManager factory.
func RegisterContextManager(name string, factory ContextManagerFactory) error {
if name == "" {
return fmt.Errorf("context manager name is required")
}
if factory == nil {
return fmt.Errorf("context manager %q factory is nil", name)
}
cmRegistryMu.Lock()
defer cmRegistryMu.Unlock()
if _, exists := cmRegistry[name]; exists {
return fmt.Errorf("context manager %q is already registered", name)
}
cmRegistry[name] = factory
return nil
}
func lookupContextManager(name string) (ContextManagerFactory, bool) {
cmRegistryMu.RLock()
defer cmRegistryMu.RUnlock()
f, ok := cmRegistry[name]
return f, ok
}