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

Rust
#[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:

Rust
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,
}
  • turn tells the tool which profile and session it is working for. The Memory plugin uses turn.profile_id to keep each profile’s notes apart.
  • agent lets a tool run child turns on the agent that is running it. That is how delegate_task works, with no special wiring.
  • cancel fires when the turn is stopped. Long-running tools should watch it.

Errors

Rust
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:

config.toml
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.

Edit this page on GitHub

Esc