mirror of
https://github.com/YspCoder/clawgo.git
synced 2026-05-01 19:07:34 +08:00
Refine agent config schema and prompt file loading
This commit is contained in:
@@ -34,15 +34,21 @@ type AgentsConfig struct {
|
||||
}
|
||||
|
||||
type AgentRouterConfig struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
MainAgentID string `json:"main_agent_id,omitempty"`
|
||||
Strategy string `json:"strategy,omitempty"`
|
||||
Rules []AgentRouteRule `json:"rules,omitempty"`
|
||||
AllowDirectAgentChat bool `json:"allow_direct_agent_chat,omitempty"`
|
||||
MaxHops int `json:"max_hops,omitempty"`
|
||||
DefaultTimeoutSec int `json:"default_timeout_sec,omitempty"`
|
||||
DefaultWaitReply bool `json:"default_wait_reply,omitempty"`
|
||||
StickyThreadOwner bool `json:"sticky_thread_owner,omitempty"`
|
||||
Enabled bool `json:"enabled"`
|
||||
MainAgentID string `json:"main_agent_id,omitempty"`
|
||||
Strategy string `json:"strategy,omitempty"`
|
||||
Policy AgentRouterPolicyConfig `json:"policy,omitempty"`
|
||||
Rules []AgentRouteRule `json:"rules,omitempty"`
|
||||
AllowDirectAgentChat bool `json:"allow_direct_agent_chat,omitempty"`
|
||||
MaxHops int `json:"max_hops,omitempty"`
|
||||
DefaultTimeoutSec int `json:"default_timeout_sec,omitempty"`
|
||||
DefaultWaitReply bool `json:"default_wait_reply,omitempty"`
|
||||
StickyThreadOwner bool `json:"sticky_thread_owner,omitempty"`
|
||||
}
|
||||
|
||||
type AgentRouterPolicyConfig struct {
|
||||
IntentMaxInputChars int `json:"intent_max_input_chars" env:"CLAWGO_INTENT_MAX_INPUT_CHARS"`
|
||||
MaxRoundsWithoutUser int `json:"max_rounds_without_user" env:"CLAWGO_AUTOLEARN_MAX_ROUNDS_WITHOUT_USER"`
|
||||
}
|
||||
|
||||
type AgentRouteRule struct {
|
||||
@@ -66,6 +72,7 @@ type SubagentConfig struct {
|
||||
Role string `json:"role,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
SystemPrompt string `json:"system_prompt,omitempty"`
|
||||
SystemPromptFile string `json:"system_prompt_file,omitempty"`
|
||||
MemoryNamespace string `json:"memory_namespace,omitempty"`
|
||||
AcceptFrom []string `json:"accept_from,omitempty"`
|
||||
CanTalkTo []string `json:"can_talk_to,omitempty"`
|
||||
@@ -94,15 +101,16 @@ type SubagentRuntimeConfig struct {
|
||||
}
|
||||
|
||||
type AgentDefaults struct {
|
||||
Workspace string `json:"workspace" env:"CLAWGO_AGENTS_DEFAULTS_WORKSPACE"`
|
||||
Proxy string `json:"proxy" env:"CLAWGO_AGENTS_DEFAULTS_PROXY"`
|
||||
ProxyFallbacks []string `json:"proxy_fallbacks" env:"CLAWGO_AGENTS_DEFAULTS_PROXY_FALLBACKS"`
|
||||
MaxTokens int `json:"max_tokens" env:"CLAWGO_AGENTS_DEFAULTS_MAX_TOKENS"`
|
||||
Temperature float64 `json:"temperature" env:"CLAWGO_AGENTS_DEFAULTS_TEMPERATURE"`
|
||||
MaxToolIterations int `json:"max_tool_iterations" env:"CLAWGO_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"`
|
||||
Heartbeat HeartbeatConfig `json:"heartbeat"`
|
||||
ContextCompaction ContextCompactionConfig `json:"context_compaction"`
|
||||
RuntimeControl RuntimeControlConfig `json:"runtime_control"`
|
||||
Workspace string `json:"workspace" env:"CLAWGO_AGENTS_DEFAULTS_WORKSPACE"`
|
||||
Proxy string `json:"proxy" env:"CLAWGO_AGENTS_DEFAULTS_PROXY"`
|
||||
ProxyFallbacks []string `json:"proxy_fallbacks" env:"CLAWGO_AGENTS_DEFAULTS_PROXY_FALLBACKS"`
|
||||
MaxTokens int `json:"max_tokens" env:"CLAWGO_AGENTS_DEFAULTS_MAX_TOKENS"`
|
||||
Temperature float64 `json:"temperature" env:"CLAWGO_AGENTS_DEFAULTS_TEMPERATURE"`
|
||||
MaxToolIterations int `json:"max_tool_iterations" env:"CLAWGO_AGENTS_DEFAULTS_MAX_TOOL_ITERATIONS"`
|
||||
Heartbeat HeartbeatConfig `json:"heartbeat"`
|
||||
ContextCompaction ContextCompactionConfig `json:"context_compaction"`
|
||||
Execution AgentExecutionConfig `json:"execution"`
|
||||
SummaryPolicy SystemSummaryPolicyConfig `json:"summary_policy"`
|
||||
}
|
||||
|
||||
type HeartbeatConfig struct {
|
||||
@@ -112,14 +120,11 @@ type HeartbeatConfig struct {
|
||||
PromptTemplate string `json:"prompt_template" env:"CLAWGO_AGENTS_DEFAULTS_HEARTBEAT_PROMPT_TEMPLATE"`
|
||||
}
|
||||
|
||||
type RuntimeControlConfig struct {
|
||||
IntentMaxInputChars int `json:"intent_max_input_chars" env:"CLAWGO_INTENT_MAX_INPUT_CHARS"`
|
||||
AutoLearnMaxRoundsWithoutUser int `json:"autolearn_max_rounds_without_user" env:"CLAWGO_AUTOLEARN_MAX_ROUNDS_WITHOUT_USER"`
|
||||
RunStateTTLSeconds int `json:"run_state_ttl_seconds" env:"CLAWGO_RUN_STATE_TTL_SECONDS"`
|
||||
RunStateMax int `json:"run_state_max" env:"CLAWGO_RUN_STATE_MAX"`
|
||||
ToolParallelSafeNames []string `json:"tool_parallel_safe_names"`
|
||||
ToolMaxParallelCalls int `json:"tool_max_parallel_calls"`
|
||||
SystemSummary SystemSummaryPolicyConfig `json:"system_summary"`
|
||||
type AgentExecutionConfig struct {
|
||||
RunStateTTLSeconds int `json:"run_state_ttl_seconds" env:"CLAWGO_RUN_STATE_TTL_SECONDS"`
|
||||
RunStateMax int `json:"run_state_max" env:"CLAWGO_RUN_STATE_MAX"`
|
||||
ToolParallelSafeNames []string `json:"tool_parallel_safe_names"`
|
||||
ToolMaxParallelCalls int `json:"tool_max_parallel_calls"`
|
||||
}
|
||||
|
||||
type SystemSummaryPolicyConfig struct {
|
||||
@@ -397,28 +402,30 @@ func DefaultConfig() *Config {
|
||||
MaxSummaryChars: 6000,
|
||||
MaxTranscriptChars: 20000,
|
||||
},
|
||||
RuntimeControl: RuntimeControlConfig{
|
||||
IntentMaxInputChars: 1200,
|
||||
AutoLearnMaxRoundsWithoutUser: 200,
|
||||
RunStateTTLSeconds: 1800,
|
||||
RunStateMax: 500,
|
||||
ToolParallelSafeNames: []string{"read_file", "list_files", "find_files", "grep_files", "memory_search", "web_search", "repo_map", "system_info"},
|
||||
ToolMaxParallelCalls: 2,
|
||||
SystemSummary: SystemSummaryPolicyConfig{
|
||||
Marker: "## System Task Summary",
|
||||
CompletedPrefix: "- Completed:",
|
||||
ChangesPrefix: "- Changes:",
|
||||
OutcomePrefix: "- Outcome:",
|
||||
CompletedTitle: "Completed Actions",
|
||||
ChangesTitle: "Change Summaries",
|
||||
OutcomesTitle: "Execution Outcomes",
|
||||
},
|
||||
Execution: AgentExecutionConfig{
|
||||
RunStateTTLSeconds: 1800,
|
||||
RunStateMax: 500,
|
||||
ToolParallelSafeNames: []string{"read_file", "list_files", "find_files", "grep_files", "memory_search", "web_search", "repo_map", "system_info"},
|
||||
ToolMaxParallelCalls: 2,
|
||||
},
|
||||
SummaryPolicy: SystemSummaryPolicyConfig{
|
||||
Marker: "## System Task Summary",
|
||||
CompletedPrefix: "- Completed:",
|
||||
ChangesPrefix: "- Changes:",
|
||||
OutcomePrefix: "- Outcome:",
|
||||
CompletedTitle: "Completed Actions",
|
||||
ChangesTitle: "Change Summaries",
|
||||
OutcomesTitle: "Execution Outcomes",
|
||||
},
|
||||
},
|
||||
Router: AgentRouterConfig{
|
||||
Enabled: false,
|
||||
MainAgentID: "main",
|
||||
Strategy: "rules_first",
|
||||
Enabled: false,
|
||||
MainAgentID: "main",
|
||||
Strategy: "rules_first",
|
||||
Policy: AgentRouterPolicyConfig{
|
||||
IntentMaxInputChars: 1200,
|
||||
MaxRoundsWithoutUser: 200,
|
||||
},
|
||||
Rules: []AgentRouteRule{},
|
||||
AllowDirectAgentChat: false,
|
||||
MaxHops: 6,
|
||||
|
||||
@@ -2,6 +2,7 @@ package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
@@ -17,43 +18,38 @@ func Validate(cfg *Config) []error {
|
||||
if cfg.Agents.Defaults.MaxToolIterations <= 0 {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.max_tool_iterations must be > 0"))
|
||||
}
|
||||
rc := cfg.Agents.Defaults.RuntimeControl
|
||||
if rc.IntentMaxInputChars < 200 {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.intent_max_input_chars must be >= 200"))
|
||||
exec := cfg.Agents.Defaults.Execution
|
||||
if exec.RunStateTTLSeconds < 60 {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.execution.run_state_ttl_seconds must be >= 60"))
|
||||
}
|
||||
if rc.AutoLearnMaxRoundsWithoutUser <= 0 {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.autolearn_max_rounds_without_user must be > 0"))
|
||||
if exec.RunStateMax <= 0 {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.execution.run_state_max must be > 0"))
|
||||
}
|
||||
if rc.RunStateTTLSeconds < 60 {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.run_state_ttl_seconds must be >= 60"))
|
||||
errs = append(errs, validateNonEmptyStringList("agents.defaults.execution.tool_parallel_safe_names", exec.ToolParallelSafeNames)...)
|
||||
if exec.ToolMaxParallelCalls <= 0 {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.execution.tool_max_parallel_calls must be > 0"))
|
||||
}
|
||||
if rc.RunStateMax <= 0 {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.run_state_max must be > 0"))
|
||||
summary := cfg.Agents.Defaults.SummaryPolicy
|
||||
if strings.TrimSpace(summary.Marker) == "" {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.summary_policy.marker must be non-empty"))
|
||||
}
|
||||
errs = append(errs, validateNonEmptyStringList("agents.defaults.runtime_control.tool_parallel_safe_names", rc.ToolParallelSafeNames)...)
|
||||
if rc.ToolMaxParallelCalls <= 0 {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.tool_max_parallel_calls must be > 0"))
|
||||
if strings.TrimSpace(summary.CompletedPrefix) == "" {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.summary_policy.completed_prefix must be non-empty"))
|
||||
}
|
||||
if strings.TrimSpace(rc.SystemSummary.Marker) == "" {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.system_summary.marker must be non-empty"))
|
||||
if strings.TrimSpace(summary.ChangesPrefix) == "" {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.summary_policy.changes_prefix must be non-empty"))
|
||||
}
|
||||
if strings.TrimSpace(rc.SystemSummary.CompletedPrefix) == "" {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.system_summary.completed_prefix must be non-empty"))
|
||||
if strings.TrimSpace(summary.OutcomePrefix) == "" {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.summary_policy.outcome_prefix must be non-empty"))
|
||||
}
|
||||
if strings.TrimSpace(rc.SystemSummary.ChangesPrefix) == "" {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.system_summary.changes_prefix must be non-empty"))
|
||||
if strings.TrimSpace(summary.CompletedTitle) == "" {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.summary_policy.completed_title must be non-empty"))
|
||||
}
|
||||
if strings.TrimSpace(rc.SystemSummary.OutcomePrefix) == "" {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.system_summary.outcome_prefix must be non-empty"))
|
||||
if strings.TrimSpace(summary.ChangesTitle) == "" {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.summary_policy.changes_title must be non-empty"))
|
||||
}
|
||||
if strings.TrimSpace(rc.SystemSummary.CompletedTitle) == "" {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.system_summary.completed_title must be non-empty"))
|
||||
}
|
||||
if strings.TrimSpace(rc.SystemSummary.ChangesTitle) == "" {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.system_summary.changes_title must be non-empty"))
|
||||
}
|
||||
if strings.TrimSpace(rc.SystemSummary.OutcomesTitle) == "" {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.runtime_control.system_summary.outcomes_title must be non-empty"))
|
||||
if strings.TrimSpace(summary.OutcomesTitle) == "" {
|
||||
errs = append(errs, fmt.Errorf("agents.defaults.summary_policy.outcomes_title must be non-empty"))
|
||||
}
|
||||
hb := cfg.Agents.Defaults.Heartbeat
|
||||
if hb.Enabled {
|
||||
@@ -219,6 +215,12 @@ func Validate(cfg *Config) []error {
|
||||
func validateAgentRouter(cfg *Config) []error {
|
||||
router := cfg.Agents.Router
|
||||
var errs []error
|
||||
if router.Policy.IntentMaxInputChars < 200 {
|
||||
errs = append(errs, fmt.Errorf("agents.router.policy.intent_max_input_chars must be >= 200"))
|
||||
}
|
||||
if router.Policy.MaxRoundsWithoutUser <= 0 {
|
||||
errs = append(errs, fmt.Errorf("agents.router.policy.max_rounds_without_user must be > 0"))
|
||||
}
|
||||
if strings.TrimSpace(router.Strategy) != "" {
|
||||
switch strings.TrimSpace(router.Strategy) {
|
||||
case "rules_first", "round_robin", "manual":
|
||||
@@ -320,6 +322,14 @@ func validateSubagents(cfg *Config) []error {
|
||||
if raw.Tools.MaxParallelCalls < 0 {
|
||||
errs = append(errs, fmt.Errorf("agents.subagents.%s.tools.max_parallel_calls must be >= 0", id))
|
||||
}
|
||||
if promptFile := strings.TrimSpace(raw.SystemPromptFile); promptFile != "" {
|
||||
if filepath.IsAbs(promptFile) {
|
||||
errs = append(errs, fmt.Errorf("agents.subagents.%s.system_prompt_file must be relative", id))
|
||||
}
|
||||
if cleaned := filepath.Clean(promptFile); strings.HasPrefix(cleaned, "..") {
|
||||
errs = append(errs, fmt.Errorf("agents.subagents.%s.system_prompt_file must stay within workspace", id))
|
||||
}
|
||||
}
|
||||
if proxy := strings.TrimSpace(raw.Runtime.Proxy); proxy != "" && !providerExists(cfg, proxy) {
|
||||
errs = append(errs, fmt.Errorf("agents.subagents.%s.runtime.proxy %q not found in providers", id, proxy))
|
||||
}
|
||||
|
||||
@@ -42,3 +42,20 @@ func TestValidateSubagentsRejectsUnknownPeer(t *testing.T) {
|
||||
t.Fatalf("expected validation errors")
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSubagentsRejectsAbsolutePromptFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
cfg := DefaultConfig()
|
||||
cfg.Agents.Subagents["coder"] = SubagentConfig{
|
||||
Enabled: true,
|
||||
SystemPromptFile: "/tmp/AGENT.md",
|
||||
Runtime: SubagentRuntimeConfig{
|
||||
Proxy: "proxy",
|
||||
},
|
||||
}
|
||||
|
||||
if errs := Validate(cfg); len(errs) == 0 {
|
||||
t.Fatalf("expected validation errors")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user