koda_core/agent.rs
1//! KodaAgent — shared, immutable agent resources.
2//!
3//! Holds everything that's constant across turns within a session:
4//! tools, system prompt, project root. Shareable via `Arc`
5//! for parallel sub-agents.
6//!
7//! Note: `KodaConfig` is NOT stored here because the REPL allows
8//! switching models and providers mid-session. Config lives on the
9//! caller side and is passed to `KodaSession` per-turn.
10//!
11//! ## Built-in agents
12//!
13//! Koda ships with these built-in agents (see [`crate::config`] module):
14//!
15//! | Agent | Purpose | Write access |
16//! |---|---|---|
17//! | **default** | Main interactive agent with all tools | Yes |
18//! | **task** | General-purpose worker for delegated tasks | Yes |
19//! | **explore** | Read-only code search specialist | No |
20//! | **plan** | Architecture and planning specialist | No |
21//! | **verify** | Code review and verification | No |
22//!
23//! ## Custom agents
24//!
25//! Define agents as JSON files in `agents/` (project) or `~/.config/koda/agents/` (global):
26//!
27//! ```json
28//! {
29//! "name": "testgen",
30//! "system_prompt": "You are a test generation specialist.",
31//! "model": "gemini-2.5-flash",
32//! "write_access": true,
33//! "allowed_tools": ["Read", "Write", "Edit", "Bash", "Grep", "Glob"]
34//! }
35//! ```
36//!
37//! See [`crate::config::AgentConfig`] for all available fields.
38
39use crate::config::KodaConfig;
40use crate::memory;
41use crate::providers::ToolDefinition;
42use crate::tools::ToolRegistry;
43
44use anyhow::Result;
45use std::path::PathBuf;
46
47/// Shared agent resources. Immutable after construction.
48///
49/// Create once, share via `Arc<KodaAgent>` across sessions and sub-agents.
50pub struct KodaAgent {
51 /// Project root directory.
52 pub project_root: PathBuf,
53 /// Tool registry with all built-in tools.
54 pub tools: ToolRegistry,
55 /// Pre-computed tool definitions for the LLM.
56 pub tool_defs: Vec<ToolDefinition>,
57 /// Assembled system prompt.
58 pub system_prompt: String,
59}
60
61impl KodaAgent {
62 /// Build a new agent from config and project root.
63 ///
64 /// `commands` is a list of `(name, description)` pairs for user-facing
65 /// slash commands. The CLI passes its `SLASH_COMMANDS` registry here;
66 /// sub-agents pass `&[]`.
67 pub async fn new(
68 config: &KodaConfig,
69 project_root: PathBuf,
70 commands: &[(&str, &str)],
71 ) -> Result<Self> {
72 let tools = ToolRegistry::with_trust(
73 project_root.clone(),
74 config.max_context_tokens,
75 config.trust,
76 );
77 let tool_defs = tools.get_definitions(&config.allowed_tools, &config.disallowed_tools);
78
79 let semantic_memory = memory::load(&project_root)?;
80 let env = crate::prompt::EnvironmentInfo {
81 project_root: &project_root,
82 model: &config.model,
83 platform: std::env::consts::OS,
84 };
85 let system_prompt = crate::prompt::build_system_prompt(
86 &config.system_prompt,
87 &semantic_memory,
88 &config.agents_dir,
89 &env,
90 commands,
91 &tools.skill_registry,
92 );
93
94 Ok(Self {
95 project_root,
96 tools,
97 tool_defs,
98 system_prompt,
99 })
100 }
101
102 /// Rebuild the system prompt from the agent's current skill registry.
103 ///
104 /// Call this after injecting additional skills (e.g. `inject_builtin_skills`)
105 /// so the rebuilt prompt includes all available skills in the `## Skills` section.
106 ///
107 /// Note: MCP server instructions are NOT included here — they are composed
108 /// per-turn in `KodaSession::run_turn` via `render_mcp_instructions_section`,
109 /// because MCP servers may attach after this static prompt is built (#922).
110 pub fn rebuild_system_prompt(&mut self, config: &KodaConfig, commands: &[(&str, &str)]) {
111 let semantic_memory = memory::load(&self.project_root).unwrap_or_default();
112 let env = crate::prompt::EnvironmentInfo {
113 project_root: &self.project_root,
114 model: &config.model,
115 platform: std::env::consts::OS,
116 };
117 self.system_prompt = crate::prompt::build_system_prompt(
118 &config.system_prompt,
119 &semantic_memory,
120 &config.agents_dir,
121 &env,
122 commands,
123 &self.tools.skill_registry,
124 );
125 }
126
127 /// Compact MCP status for the TUI status bar.
128 ///
129 /// Returns `None` if no MCP servers are configured.
130 pub fn mcp_status_bar_info(&self) -> Option<crate::mcp::manager::McpStatusBarInfo> {
131 let mgr = self.tools.mcp_manager()?;
132 let guard = mgr.try_read().ok()?;
133 guard.status_bar_summary()
134 }
135}