Tools
Tools are what the model can call. Each declares a name, a JSON Schema for its arguments, and a risk.
A tool is anything the model can ask the agent to do: search past conversations, save a memory, hand work to a helper. In CommonOS, tools come from plugins, and each profile can be limited to a list of them.
The Tool trait
#[async_trait]
pub trait Tool: Send + Sync {
fn name(&self) -> &str;
fn description(&self) -> &str;
/// JSON Schema for the arguments.
fn schema(&self) -> Value;
fn risk(&self) -> Risk;
/// Interactive tools force sequential dispatch (Hermes: `clarify`).
fn interactive(&self) -> bool {
false
}
async fn call(&self, ctx: &ToolContext, args: Value) -> Result<String, ToolError>;
}The model sees the name, description and schema. Write the description for the model: say what the tool does, when to use it, and what the arguments mean.
Risk
Every tool declares how risky it is. The built-in approval policy reads it before each call.
| Risk | Behavior | Use it for |
|---|---|---|
| read | Runs without asking | Looking things up; changing only the agent’s own notes |
| write | Asks a person first | Anything that changes the outside world: sending email, editing a calendar, paying |
| deny | Never runs | Tools that are installed but switched off |
The shipped tools are all read: memory only changes the agent’s own notes, session_search only reads, and delegate_task only runs more turns. A calendar or email plugin should use write.
ToolContext
Every call gets a context:
pub struct ToolContext {
pub turn: TurnInfo,
pub model: String,
/// The agent running this turn. Weak because the agent owns its tools.
pub agent: Weak<CommonAgent>,
pub cancel: CancellationToken,
}turntells the tool which profile and session it is working for. The Memory plugin usesturn.profile_idto keep each profile’s notes apart.agentlets a tool run child turns on the agent that is running it. That is howdelegate_taskworks, with no special wiring.cancelfires when the turn is stopped. Long-running tools should watch it.
Errors
pub enum ToolError {
InvalidArgs(String), // "invalid arguments: …"
Failed(String),
}An error goes back to the model as a tool result with is_error: true, so the model can correct itself and try again. It does not end the turn.
The registry
ToolRegistry holds tools by name.
| Method | Does |
|---|---|
register(tool) |
Adds a tool. |
get(name) |
Looks one up. |
names() / specs() |
Lists them, sorted by name. specs() is what the model sees. |
only(&names) |
A copy with just these tools. Fails with the first missing name. |
without(&names) |
A copy without these tools. Used for child agents. |
Tools per profile
A profile’s config.toml can list the tools its agent gets:
tools = ["session_search"]With no list, the agent gets every tool the enabled plugins provide. A listed tool that no plugin provides stops start-up with the profile and tool named. Child agents draw from their parent agent’s registry, so they can never reach a tool their profile doesn’t have.
Running in parallel
When the model asks for several tools in one response, they run at the same time. If any of them is interactive, they run one at a time instead. Either way, results go back to the model in the order it asked.