Skip to main content

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}