Tool trait. There are three idiomatic ways to write one, each more convenient than the last when you can use it.
What a tool is
name— what the model will type to call you.description— what the model reads to decide when to call you. Make it good.args_schema— JSON Schema for the arguments. The provider sends this to the model.return_direct— whentrue, the tool’s output becomes the final response (skips the next model turn)._run— the body. ReceivesToolInput, returnsToolOutput.
When to reach for which option
| You have… | Use |
|---|---|
| A simple async function with typed inputs | #[cognis::tool] macro |
| A struct with config (HTTP client, DB pool) and a typed input | SchemaBasedTool trait |
| Anything more custom | Hand-written impl Tool |
Quick example — typed SchemaBasedTool
The most common shape: typed Params, derive JsonSchema, implement one method.
SchemaBasedTool provides a blanket Tool impl that handles serde, validation, and the JSON Schema for you.
Quick example — hand-written Tool
When you need control over the schema or want to handle raw ToolInput:
examples/tools/derive_tool.rs.
Wiring tools into an agent
Tools areArc<dyn Tool>. The builder accepts one or many.
ToolAllowList, ToolDenyList).
Validators
When using#[cognis::tool] or SchemaBasedTool, you can declare per-field validators that emit both runtime checks and JSON Schema constraints:
| Attribute | Effect |
|---|---|
#[schema(range(min = 0, max = 100))] | Numeric bounds. |
#[schema(length(min = 1, max = 200))] | String / Vec length. |
#[schema(pattern("^[A-Z][a-z]+$"))] | Regex. |
#[schema(enum_values("low", "medium", "high"))] | Enum. |
#[schema(format("email"))] | JSON Schema format. |
Built-in tools
Cognis ships a few generally useful tools undercognis::tools::*:
Calculator— pure-Rust expression evaluator (+ - * / % ^, parens, unary-). No code execution.FilesystemToolfamily — read/write/edit/list a workspace, gated by aBackend.HttpRequest(featuretools-http) — make HTTP calls with allow-listed hosts.JsonQuery— JMESPath-like query over JSON.PythonRepl— sandboxed Python execution (where the runtime is available).OpenApiTool— generate tools from an OpenAPI spec.HumanTool— ask a human a question, return their reply.
ToolOrchestrator (no-loop DAG)
When you need to call a fixed set of tools in a known DAG — not an agent loop — useToolOrchestrator:
How it works
- The model never sees Rust types directly. It sees the JSON Schema you produce. Make descriptions and field docs clear — that’s the model’s only signal.
- Tool dispatch is a node in the agent graph. When the model emits tool calls, the dispatcher node parses arguments, runs the tool, and threads the result back as a
Message::toolentry tied to the call id. - Errors are tool-shaped. Returning an error from
_runbecomes aToolMessagewith the error text — the agent sees it and can recover or apologize.
See also
Models and providers
The other half of the agent — the LLM that picks the tool.
Human-in-the-loop
Require approval before sensitive tools run.
Middleware
ToolAllowList, ToolDenyList, ToolRetry, ToolEmulator.Patterns → HITL approval
A worked tool-approval flow.