Skip to content
Open
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
31 changes: 31 additions & 0 deletions core/agent/actions.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,37 @@ func (a *Agent) availableActions(j *types.Job) types.Actions {
return defaultActions
}

// filterActions constrains a set of actions to a per-agent allow/deny-list by tool name.
// If allow is non-empty, only actions whose name is in allow survive; names in deny are
// always removed. Empty allow and deny → input is returned unchanged (backward compatible).
func filterActions(acts types.Actions, allow, deny []string) types.Actions {
if len(allow) == 0 && len(deny) == 0 {
return acts
}
allowSet := make(map[string]struct{}, len(allow))
for _, n := range allow {
allowSet[n] = struct{}{}
}
denySet := make(map[string]struct{}, len(deny))
for _, n := range deny {
denySet[n] = struct{}{}
}
out := make(types.Actions, 0, len(acts))
for _, act := range acts {
name := string(act.Definition().Name)
if len(allowSet) > 0 {
if _, ok := allowSet[name]; !ok {
continue
}
}
if _, ok := denySet[name]; ok {
continue
}
out = append(out, act)
}
return out
}

func (a *Agent) prepareHUD() (promptHUD *PromptHUD) {
if !a.options.enableHUD {
return nil
Expand Down
7 changes: 6 additions & 1 deletion core/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -858,8 +858,13 @@ func (a *Agent) consumeJob(job *types.Job, role string) {
}

availableActions := a.getAvailableActionsForJob(job)
// Per-agent tool allow/deny-list: constrain the EFFECTIVE tool set across all
// sources (built-in, KB-injected and MCP) at this single assembly point, so both
// the tools presented to the model (cogitoTools) and the lookup set (allActions)
// respect it. Empty allow/deny = unchanged behaviour.
availableActions = filterActions(availableActions, a.options.allowedTools, a.options.excludedTools)
cogitoTools := availableActions.ToCogitoTools(job.GetContext(), a.sharedState)
allActions := append(availableActions, a.mcpActionDefinitions...)
allActions := append(availableActions, filterActions(a.mcpActionDefinitions, a.options.allowedTools, a.options.excludedTools)...)

obs := job.Obs
if obs == nil && a.observer != nil && job.Obs != nil {
Expand Down
12 changes: 12 additions & 0 deletions core/agent/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type options struct {
randomIdentity bool
userActions types.Actions
jobFilters types.JobFilters
allowedTools, excludedTools []string
enableHUD, standaloneJob, showCharacter, enableKB, enableSummaryMemory, enableLongTermMemory bool
stripThinkingTags bool
kbAutoSearch bool
Expand Down Expand Up @@ -512,6 +513,17 @@ func WithKBAutoSearch(enabled bool) Option {
}
}

// WithToolFilter constrains the agent's effective tool set. If allow is non-empty,
// only those tool names survive; deny names are always removed. Applied across ALL
// tool sources (MCP, built-in, KB-injected) at the single assembly point.
func WithToolFilter(allow, deny []string) Option {
return func(o *options) error {
o.allowedTools = allow
o.excludedTools = deny
return nil
}
}

// WithSchedulerStorePath sets the path for the scheduler's JSON storage file
func WithSchedulerStorePath(path string) Option {
return func(o *options) error {
Expand Down
5 changes: 5 additions & 0 deletions core/state/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ type AgentConfig struct {
StripThinkingTags bool `json:"strip_thinking_tags" form:"strip_thinking_tags"`
EnableEvaluation bool `json:"enable_evaluation" form:"enable_evaluation"`
MaxEvaluationLoops int `json:"max_evaluation_loops" form:"max_evaluation_loops"`
// AllowedTools/ExcludedTools constrain the agent's EFFECTIVE tool set (across MCP,
// built-in and KB-injected actions). If AllowedTools is non-empty, only those tool
// names are kept; ExcludedTools are always removed. Empty = unchanged behaviour.
AllowedTools []string `json:"allowed_tools" form:"allowed_tools"`
ExcludedTools []string `json:"excluded_tools" form:"excluded_tools"`
}

type AgentConfigMeta struct {
Expand Down
4 changes: 4 additions & 0 deletions core/state/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,10 @@ func (a *AgentPool) startAgentWithConfig(name, pooldir string, config *AgentConf
opts = append(opts, EnableGuidedTools)
}

if len(config.AllowedTools) > 0 || len(config.ExcludedTools) > 0 {
opts = append(opts, WithToolFilter(config.AllowedTools, config.ExcludedTools))
}

if config.StripThinkingTags {
opts = append(opts, EnableStripThinkingTags)
}
Expand Down