Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions core/agent/knowledgebase.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package agent

import (
"context"
"fmt"
"os"
"path/filepath"
Expand All @@ -10,6 +11,7 @@ import (
"github.com/mudler/cogito"
"github.com/mudler/xlog"
"github.com/sashabaranov/go-openai"
"github.com/sashabaranov/go-openai/jsonschema"
)

func (a *Agent) knowledgeBaseLookup(job *types.Job, conv Messages) Messages {
Expand All @@ -19,6 +21,10 @@ func (a *Agent) knowledgeBaseLookup(job *types.Job, conv Messages) Messages {
xlog.Debug("[Knowledge Base Lookup] Disabled, skipping", "agent", a.Character.Name)
return conv
}
if !a.options.kbAutoSearch {
xlog.Debug("[Knowledge Base Lookup] Auto search disabled, skipping", "agent", a.Character.Name)
return conv
}
Comment on lines +24 to +27
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kbAutoSearch changes the observable behavior of the agent (KB recall is now skipped when disabled), but there are no tests covering this new branch. Since core/agent already has a test suite, please add unit tests asserting that knowledgeBaseLookup does not call into ragdb.Search when kbAutoSearch is false (and does when true).

Copilot uses AI. Check for mistakes.
if a.options.ragdb == nil {
xlog.Debug("[Knowledge Base Lookup] No RAG DB configured, skipping", "agent", a.Character.Name)
return conv
Expand Down Expand Up @@ -151,3 +157,124 @@ func (a *Agent) saveCurrentConversation(conv Messages) {
}
}
}

// KBWrapperActions wraps RAGDB functionality as actions
type KBWrapperActions struct {
ragdb RAGDB
kbResults int
}

type SearchKnowledgeBaseAction struct {
*KBWrapperActions
}

type AddToKnowledgeBaseAction struct {
*KBWrapperActions
}

// NewKBWrapperActions creates factory functions for KB wrapper actions
func NewKBWrapperActions(ragdb RAGDB, kbResults int) (*SearchKnowledgeBaseAction, *AddToKnowledgeBaseAction) {
wrapper := &KBWrapperActions{
ragdb: ragdb,
kbResults: kbResults,
}
return &SearchKnowledgeBaseAction{wrapper}, &AddToKnowledgeBaseAction{wrapper}
}

func (a *SearchKnowledgeBaseAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
if a.ragdb == nil {
return types.ActionResult{}, fmt.Errorf("knowledge base is not configured")
}

var req struct {
Query string `json:"query"`
}
if err := params.Unmarshal(&req); err != nil {
return types.ActionResult{}, fmt.Errorf("invalid parameters: %w", err)
}

if req.Query == "" {
return types.ActionResult{}, fmt.Errorf("query cannot be empty")
}

results, err := a.ragdb.Search(req.Query, a.kbResults)
if err != nil {
return types.ActionResult{}, fmt.Errorf("failed to search knowledge base: %w", err)
}

if len(results) == 0 {
return types.ActionResult{
Result: fmt.Sprintf("No results found for query: %q", req.Query),
}, nil
}

formatResults := ""
for i, r := range results {
formatResults += fmt.Sprintf("%d. %s\n", i+1, r)
}

return types.ActionResult{
Result: fmt.Sprintf("Found %d result(s) for query %q:\n%s", len(results), req.Query, formatResults),
Metadata: map[string]interface{}{
"query": req.Query,
"results": results,
"count": len(results),
},
}, nil
}

func (a *SearchKnowledgeBaseAction) Definition() types.ActionDefinition {
return types.ActionDefinition{
Name: types.ActionDefinitionName("search_knowledge_base"),
Description: "Search the knowledge base for relevant information using a query string",
Properties: map[string]jsonschema.Definition{
"query": {
Type: jsonschema.String,
Description: "The search query to find relevant information in the knowledge base",
},
},
Required: []string{"query"},
}
}

func (a *AddToKnowledgeBaseAction) Run(ctx context.Context, sharedState *types.AgentSharedState, params types.ActionParams) (types.ActionResult, error) {
if a.ragdb == nil {
return types.ActionResult{}, fmt.Errorf("knowledge base is not configured")
}

var req struct {
Content string `json:"content"`
}
if err := params.Unmarshal(&req); err != nil {
return types.ActionResult{}, fmt.Errorf("invalid parameters: %w", err)
}

if req.Content == "" {
return types.ActionResult{}, fmt.Errorf("content cannot be empty")
}

if err := a.ragdb.Store(req.Content); err != nil {
return types.ActionResult{}, fmt.Errorf("failed to store content in knowledge base: %w", err)
}

return types.ActionResult{
Result: "Successfully added content to knowledge base",
Metadata: map[string]interface{}{
"content": req.Content,
},
}, nil
}

func (a *AddToKnowledgeBaseAction) Definition() types.ActionDefinition {
return types.ActionDefinition{
Name: types.ActionDefinitionName("add_to_knowledge_base"),
Description: "Add new content to the knowledge base for future retrieval",
Properties: map[string]jsonschema.Definition{
"content": {
Type: jsonschema.String,
Description: "The content to store in the knowledge base",
},
},
Required: []string{"content"},
}
}
9 changes: 9 additions & 0 deletions core/agent/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type options struct {
jobFilters types.JobFilters
enableHUD, standaloneJob, showCharacter, enableKB, enableSummaryMemory, enableLongTermMemory bool
stripThinkingTags bool
kbAutoSearch bool
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new kbAutoSearch field alignment looks off (extra spacing before bool). Running gofmt on this file will normalize formatting and avoid noisy diffs later.

Suggested change
kbAutoSearch bool
kbAutoSearch bool

Copilot uses AI. Check for mistakes.

canStopItself bool
initiateConversations bool
Expand Down Expand Up @@ -85,6 +86,7 @@ func defaultOptions() *options {
periodicRuns: 15 * time.Minute,
maxEvaluationLoops: 2,
enableEvaluation: false,
kbAutoSearch: true, // Default to true to maintain backward compatibility
LLMAPI: llmOptions{
APIURL: "http://localhost:8080",
Model: "gpt-4",
Expand Down Expand Up @@ -473,3 +475,10 @@ func WithTTSModel(model string) Option {
return nil
}
}

func WithKBAutoSearch(enabled bool) Option {
return func(o *options) error {
o.kbAutoSearch = enabled
return nil
}
}
26 changes: 22 additions & 4 deletions core/state/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,12 @@ type AgentConfig struct {
IdentityGuidance string `json:"identity_guidance" form:"identity_guidance"`
PeriodicRuns string `json:"periodic_runs" form:"periodic_runs"`
PermanentGoal string `json:"permanent_goal" form:"permanent_goal"`
EnableKnowledgeBase bool `json:"enable_kb" form:"enable_kb"`
EnableKBCompaction bool `json:"enable_kb_compaction" form:"enable_kb_compaction"`
KBCompactionInterval string `json:"kb_compaction_interval" form:"kb_compaction_interval"`
KBCompactionSummarize bool `json:"kb_compaction_summarize" form:"kb_compaction_summarize"`
EnableKnowledgeBase bool `json:"enable_kb" form:"enable_kb"`
EnableKBCompaction bool `json:"enable_kb_compaction" form:"enable_kb_compaction"`
KBCompactionInterval string `json:"kb_compaction_interval" form:"kb_compaction_interval"`
KBCompactionSummarize bool `json:"kb_compaction_summarize" form:"kb_compaction_summarize"`
KBAutoSearch bool `json:"kb_auto_search" form:"kb_auto_search"`
KBAsTools bool `json:"kb_as_tools" form:"kb_as_tools"`
EnableReasoning bool `json:"enable_reasoning" form:"enable_reasoning"`
EnableGuidedTools bool `json:"enable_guided_tools" form:"enable_guided_tools"`
KnowledgeBaseResults int `json:"kb_results" form:"kb_results"`
Expand Down Expand Up @@ -255,6 +257,22 @@ func NewAgentConfigMeta(
DefaultValue: false,
Tags: config.Tags{Section: "MemorySettings"},
},
{
Name: "kb_auto_search",
Label: "KB Auto Search",
Type: "checkbox",
DefaultValue: true,
HelpText: "Automatically search knowledge base when a user message is received",
Tags: config.Tags{Section: "MemorySettings"},
},
{
Name: "kb_as_tools",
Label: "KB As Tools",
Type: "checkbox",
DefaultValue: false,
HelpText: "Inject knowledge base search and add actions as tools, allowing the agent to access its memory without manual configuration",
Tags: config.Tags{Section: "MemorySettings"},
},
{
Name: "system_prompt",
Label: "System Prompt",
Expand Down
18 changes: 18 additions & 0 deletions core/state/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,24 @@ func (a *AgentPool) startAgentWithConfig(name string, config *AgentConfig, obs O
summarize := config.KBCompactionSummarize
opts = append(opts, EnableKBCompaction, WithKBCompactionInterval(interval), WithKBCompactionSummarize(summarize))
}
// Set KB auto search option (defaults to true for backward compatibility)
// For backward compatibility: if both new KB fields are false (zero values),
// assume this is an old config and default KBAutoSearch to true
kbAutoSearch := config.KBAutoSearch
if !config.KBAutoSearch && !config.KBAsTools {
// Both new fields are false, likely an old config - default to true for backward compatibility
kbAutoSearch = true
}
opts = append(opts, WithKBAutoSearch(kbAutoSearch))
Comment on lines +568 to +576
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backward-compatibility fallback (if !config.KBAutoSearch && !config.KBAsTools { kbAutoSearch = true }) makes it impossible to explicitly configure both kb_auto_search=false and kb_as_tools=false (it will always be forced back to true). If disabling KB access modes is a valid configuration, consider detecting field presence during JSON unmarshal (e.g., use *bool or json.RawMessage presence checks in AgentConfig.UnmarshalJSON) so you can default to true only when the field is absent, not when it is explicitly set to false.

Copilot uses AI. Check for mistakes.
// Inject KB wrapper actions if enabled
if config.KBAsTools && ragClient != nil {
kbResults := config.KnowledgeBaseResults
if kbResults <= 0 {
kbResults = 5 // Default
}
searchAction, addAction := NewKBWrapperActions(ragClient, kbResults)
actions = append(actions, searchAction, addAction)
Copy link

Copilot AI Feb 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

KB wrapper actions are appended to the local actions slice after WithActions(actions...) has already been added to opts (and opts is never updated). Because WithActions captures the slice header at that moment, the later append(actions, ...) will not reliably propagate into the agent's configured actions, so kb_as_tools may have no effect. Move the KB action injection before building opts/calling WithActions, or re-apply WithActions after mutating actions (or build a finalActions slice and pass that once).

Suggested change
actions = append(actions, searchAction, addAction)
actions = append(actions, searchAction, addAction)
// Re-apply actions option so the updated slice (with KB tools) is used
opts = append(opts, WithActions(actions...))

Copilot uses AI. Check for mistakes.
}
}

if config.EnableReasoning {
Expand Down
Loading