crabtalk_core/agent/config.rs
1//! Agent configuration.
2//!
3//! [`AgentConfig`] is a serializable struct holding all agent parameters.
4//! Used by [`super::AgentBuilder`] to construct an [`super::Agent`].
5
6use crate::{AgentId, config::hooks::HooksConfig, model::ToolChoice};
7use serde::{Deserialize, Serialize};
8
9/// Default maximum iterations for agent execution.
10const DEFAULT_MAX_ITERATIONS: usize = 16;
11
12/// Default compact threshold in estimated tokens (~100k).
13const DEFAULT_COMPACT_THRESHOLD: usize = 100_000;
14
15/// Default max byte length for tool results during compaction.
16const DEFAULT_COMPACT_TOOL_MAX_LEN: usize = 1024;
17
18/// Serializable agent configuration.
19///
20/// Contains all parameters for an agent: identity, system prompt, model,
21/// iteration limits, and delegation scope. Used both as the
22/// TOML deserialization target and the runtime agent definition.
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct AgentConfig {
25 /// Stable ULID identity. A fresh ULID is generated by
26 /// [`AgentConfig::new`] / the protocol create handler when missing.
27 #[serde(default)]
28 pub id: AgentId,
29 /// Agent name. Derived from TOML key, not stored in TOML.
30 #[serde(skip)]
31 pub name: String,
32 /// Human-readable description.
33 #[serde(default)]
34 pub description: String,
35 /// System prompt sent before each LLM request. Loaded from .md file.
36 #[serde(skip)]
37 pub system_prompt: String,
38 /// Model to use from the registry. Required — every agent runs against
39 /// a specific model. Empty string is rejected by the provider registry.
40 #[serde(default)]
41 pub model: String,
42 /// Maximum iterations before stopping.
43 #[serde(default = "default_max_iterations")]
44 pub max_iterations: usize,
45 /// Controls which tool the model calls. Defaults to `Auto`.
46 #[serde(default)]
47 pub tool_choice: ToolChoice,
48 /// Whether to enable thinking/reasoning mode.
49 #[serde(default)]
50 pub thinking: bool,
51 /// Agents this agent can delegate to via spawn_task. Empty = no delegation.
52 #[serde(default)]
53 pub members: Vec<String>,
54 /// Skill names this agent can access. Empty = all skills (crabtalk default).
55 #[serde(default)]
56 pub skills: Vec<String>,
57 /// MCP server names this agent can access. Empty = all MCPs (crabtalk default).
58 #[serde(default)]
59 pub mcps: Vec<String>,
60 /// Computed tool whitelist. Empty = all tools. Not serialized.
61 #[serde(skip)]
62 pub tools: Vec<String>,
63 /// Token count threshold for automatic context compaction.
64 /// When history exceeds this, the agent compacts automatically.
65 /// None = disabled. Defaults to 100_000.
66 #[serde(default = "default_compact_threshold")]
67 pub compact_threshold: Option<usize>,
68 /// Max byte length to keep from tool-role messages when compacting.
69 /// Longer results are truncated before sending to the compaction LLM.
70 #[serde(default = "default_compact_tool_max_len")]
71 pub compact_tool_max_len: usize,
72 /// Hook configuration for this agent (bash deny rules, memory recall
73 /// limit, etc.). Each agent owns its own hook state — there is no
74 /// global override.
75 #[serde(default)]
76 pub hooks: HooksConfig,
77}
78
79fn default_max_iterations() -> usize {
80 DEFAULT_MAX_ITERATIONS
81}
82
83fn default_compact_threshold() -> Option<usize> {
84 Some(DEFAULT_COMPACT_THRESHOLD)
85}
86
87fn default_compact_tool_max_len() -> usize {
88 DEFAULT_COMPACT_TOOL_MAX_LEN
89}
90
91impl Default for AgentConfig {
92 fn default() -> Self {
93 Self {
94 id: AgentId::nil(),
95 name: String::new(),
96 description: String::new(),
97 system_prompt: String::new(),
98 model: String::new(),
99 max_iterations: DEFAULT_MAX_ITERATIONS,
100 tool_choice: ToolChoice::Auto,
101 thinking: false,
102 members: Vec::new(),
103 skills: Vec::new(),
104 mcps: Vec::new(),
105 tools: Vec::new(),
106 compact_threshold: default_compact_threshold(),
107 compact_tool_max_len: DEFAULT_COMPACT_TOOL_MAX_LEN,
108 hooks: HooksConfig::default(),
109 }
110 }
111}
112
113impl AgentConfig {
114 /// Create a new config with the given name, a fresh ULID, and
115 /// defaults for everything else.
116 pub fn new(name: impl Into<String>) -> Self {
117 Self {
118 id: AgentId::new(),
119 name: name.into(),
120 ..Default::default()
121 }
122 }
123
124 /// Set the system prompt.
125 pub fn system_prompt(mut self, prompt: impl Into<String>) -> Self {
126 self.system_prompt = prompt.into();
127 self
128 }
129
130 /// Set the description.
131 pub fn description(mut self, desc: impl Into<String>) -> Self {
132 self.description = desc.into();
133 self
134 }
135
136 /// Set the model to use from the registry.
137 pub fn model(mut self, name: impl Into<String>) -> Self {
138 self.model = name.into();
139 self
140 }
141
142 /// Enable or disable thinking/reasoning mode.
143 pub fn thinking(mut self, enabled: bool) -> Self {
144 self.thinking = enabled;
145 self
146 }
147}