pub struct ToolSpec {
pub name: String,
pub description: String,
pub schema_json: Value,
pub title: Option<String>,
pub needs_approval: bool,
pub read_only: bool,
pub destructive: bool,
pub open_world: bool,
pub cacheable_approval: bool,
}Expand description
Declaration of a tool the model may invoke.
Mirrors the MCP Tool shape so built-in and connector tools are described
uniformly: title is the MCP title annotation, and read_only /
destructive / open_world are the readOnlyHint / destructiveHint /
openWorldHint annotations. Build with ToolSpec::new + the chainable
setters rather than a struct literal.
Fields§
§name: StringUnique tool name; the model references this when emitting a ToolCall.
description: StringHuman-readable description of what the tool does.
schema_json: ValueJSON Schema object describing the tool’s argument shape.
title: Option<String>MCP-style human display name for this tool (the title annotation):
a friendly label shown to people (e.g. in an approval prompt) while the
machine-facing name stays the audit identifier.
None means no curated label was provided; callers derive a display
name from name via polyc_proto::humanize_tool_name.
needs_approval: boolIntrinsic “this tool is side-effecting / requires human approval” flag.
When true the tool must be routed through the harness’s
human-in-the-loop (HITL) approval gate before it executes, even when no
operator-side allow-list names it. Pure, read-only tools leave this
false.
This is the per-tool generalization of the old hard-coded
approval-by-name list: it maps from the MCP destructiveHint tool
annotation, so an upstream connector that advertises a destructive tool
is gated per-tool rather than per-connector.
Defaults to false and is skipped when serializing the safe default, so
older payloads that omit the field still deserialize as ungated.
read_only: boolMCP readOnlyHint: the tool does not modify its environment. Advisory —
surfaced to the model and usable by callers (e.g. sandbox-mode gating
never gates a read-only tool). Defaults to false.
destructive: boolMCP destructiveHint: the tool may perform irreversible / side-effecting
changes. Drives sandbox-mode gating (destructive tools gate in read-only
mode) and maps from a connector’s destructiveHint. Defaults to false.
open_world: boolMCP openWorldHint: the tool may interact with an open world of external
entities, so its RESULT can carry content of uncontrolled provenance. This
is the INBOUND (“untrusted content in context”) leg of the lethal
trifecta — a tool with open_world = true seeds the leg when its result
is in context (see polyc_agent’s untrusted_content_in_context). The
built-in web fetchers set it; the sandbox coding tools do not. For a
dialed connector it is read from openWorldHint at connect. Defaults to
false.
cacheable_approval: boolWhether a single human approval for this tool may be remembered for the
rest of a conversation session (per-caller) and reused for later calls,
instead of re-prompting every time. Defaults to false.
The session grant is per-tool, not per-argument: approving one call
authorizes the tool for ANY arguments for the rest of the session. So set
this ONLY when the tool’s ENTIRE argument space is safe to auto-run within
the sandbox boundary — i.e. it is both idempotent AND can’t reach anything
the human wouldn’t have blanket-approved. file_read qualifies because it
is workspace-confined (coding::workspace::resolve rejects absolute/..
paths), so “approve one read” only ever grants reads inside the sandbox.
NEVER set it on a tool that spends money, has side effects, or whose risk
varies by argument (e.g. it could read/write outside a confined root):
those must get a fresh decision per call.
Implementations§
Source§impl ToolSpec
impl ToolSpec
Sourcepub fn new(
name: impl Into<String>,
description: impl Into<String>,
schema_json: Value,
) -> Self
pub fn new( name: impl Into<String>, description: impl Into<String>, schema_json: Value, ) -> Self
A tool spec with the given name, description, and JSON-Schema
schema_json; all annotations default off. Chain the setters below to
add a title or mark it read-only / destructive / approval-gated.
Sourcepub const fn destructive(self) -> Self
pub const fn destructive(self) -> Self
Mark the tool destructive (MCP destructiveHint).
Sourcepub const fn open_world(self) -> Self
pub const fn open_world(self) -> Self
Mark the tool open-world (MCP openWorldHint): its result can carry
content of uncontrolled provenance, seeding the untrusted-content leg.
Sourcepub const fn cacheable_approval(self) -> Self
pub const fn cacheable_approval(self) -> Self
Mark a single approval for this tool as rememberable for the rest of a
conversation session (per-caller). Only set this on idempotent tools (see
Self::cacheable_approval field docs).
Sourcepub const fn approval_required(self) -> Self
pub const fn approval_required(self) -> Self
Mark the tool as intrinsically requiring HITL approval (independent of
sandbox mode — e.g. paid_fetch).