Skip to main content

agentyk_core/
capability.rs

1//! Capabilities — composable extensions that give an agent behavior.
2//!
3//! A capability contributes system-prompt text and tools. It keeps the
4//! everruns `Capability` trait shape (`id`, `system_prompt_contribution`,
5//! `tools`), with one deliberate difference: capabilities are attached to an
6//! agent **by object** (`AgentBuilder::capability(FileSystem::new())`), not
7//! registered in a registry and then referenced by string id. The string
8//! `id()` remains for diagnostics and for hosts that layer config-driven
9//! wiring on top.
10
11use std::sync::Arc;
12
13use async_trait::async_trait;
14use serde::{Deserialize, Serialize};
15
16use crate::error::Result;
17use crate::id::SessionId;
18use crate::tool::{Tool, ToolOutput};
19
20/// Context available while assembling the system prompt.
21#[derive(Debug, Clone)]
22#[non_exhaustive]
23pub struct SystemPromptContext {
24    /// The session whose prompt is being assembled, so a capability can
25    /// contribute session-specific text.
26    pub session_id: SessionId,
27}
28
29impl SystemPromptContext {
30    /// Context for assembling one session's system prompt.
31    pub fn new(session_id: SessionId) -> Self {
32        Self { session_id }
33    }
34}
35
36/// One slash-command a capability exposes — everruns' `CommandDescriptor`,
37/// pared down to what a host needs to list and invoke it.
38#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
39#[non_exhaustive]
40pub struct CommandDescriptor {
41    /// Invoked as `/{name}` (no leading slash here).
42    pub name: String,
43    /// One line describing what it does, for a host to list.
44    pub description: String,
45}
46
47impl CommandDescriptor {
48    /// Describe a command a capability answers to.
49    pub fn new(name: impl Into<String>, description: impl Into<String>) -> Self {
50        Self {
51            name: name.into(),
52            description: description.into(),
53        }
54    }
55}
56
57/// Context available while executing a command. Commands are host-invoked
58/// directly (e.g. a user typing `/goal set X`) and bypass the turn loop
59/// entirely, so there's no `turn_id` — only a session.
60#[derive(Debug, Clone)]
61#[non_exhaustive]
62pub struct CommandContext {
63    /// The session the command was invoked in.
64    pub session_id: SessionId,
65}
66
67impl CommandContext {
68    /// Context for running one command.
69    pub fn new(session_id: SessionId) -> Self {
70        Self { session_id }
71    }
72}
73
74/// A bundle of behavior attached to an agent: prompt text, tools, and
75/// slash commands.
76///
77/// Capabilities are attached **by object**, not registered and referenced by
78/// id, so a capability can hold whatever state it needs (a connection, a
79/// workspace handle) without a registry to look it up in.
80#[async_trait]
81pub trait Capability: Send + Sync {
82    /// Stable string id, e.g. `"file_system"` or `"mcp:github"`.
83    fn id(&self) -> &str;
84
85    /// Display name. Defaults to the id.
86    fn name(&self) -> &str {
87        self.id()
88    }
89
90    /// One line for a host to show alongside the name. Empty by default.
91    fn description(&self) -> &str {
92        ""
93    }
94
95    /// Generic, serializable extension hatch for host-side capability
96    /// metadata an adopting host owns — enabled/degraded `status`,
97    /// `category`, `icon`, and similar richness everruns exposes. Kept as an
98    /// opaque bag (default `Null`) rather than typed core methods, so the
99    /// contract doesn't grow per host concern; a host reads and interprets
100    /// the shape it defined.
101    fn metadata(&self) -> serde_json::Value {
102        serde_json::Value::Null
103    }
104
105    /// Text appended to the agent's system prompt, if any.
106    async fn system_prompt_contribution(&self, _context: &SystemPromptContext) -> Option<String> {
107        None
108    }
109
110    /// Tools this capability exposes to the model. Async so capabilities that
111    /// discover tools remotely (MCP) fit the same trait.
112    async fn tools(&self) -> Result<Vec<Arc<dyn Tool>>> {
113        Ok(Vec::new())
114    }
115
116    /// Slash commands this capability exposes for a host to list and route
117    /// — see `Session::commands`/`Session::execute_command` in the
118    /// framework crate. Default: none.
119    fn commands(&self) -> Vec<CommandDescriptor> {
120        Vec::new()
121    }
122
123    /// Run a command by name. Return `None` if this capability doesn't own
124    /// `name` (the host tries the next capability); `args` is the raw text
125    /// after the command name.
126    async fn execute_command(
127        &self,
128        _name: &str,
129        _args: &str,
130        _context: &CommandContext,
131    ) -> Option<ToolOutput> {
132        None
133    }
134}