Skip to content

Tools Reference

Tools are Pydantic-typed callables bound to a ToolSpec by the @tool decorator. The runtime gates every invocation through the side-effect classifier and the replay policy so deterministic replay (FR-21) and capability enforcement (NFR-7) hold uniformly across in-tree, third-party, and MCP-imported tools.

Source: src/stargraph/tools/.

ToolSpec

stargraph.ir.ToolSpec (re-exported via stargraph.tools.ToolSpec) is the canonical descriptor. The full Pydantic model (AC-9.4):

Field Type Default Description
name str required Tool short name (e.g. broker_request).
namespace str required Owner namespace; the registry key is f"{namespace}.{name}@{version}".
version str required Free-form version tag.
description str required Human-readable summary; the decorator falls back to the docstring's first line.
input_schema dict[str, object] required JSON Schema describing positional/keyword inputs.
output_schema dict[str, object] required JSON Schema describing the return payload.
side_effects SideEffects required Classifier that drives replay routing.
replay_policy ReplayPolicy must_stub Override; default derives from side_effects.
permissions list[str] [] Capability claims required to invoke (NFR-7).
idempotency_key str \| None None Optional dedupe key forwarded to the runtime.
cost_estimate Decimal \| None None FR-9 monetary estimate; never float.
examples list[dict[str, object]] [] Reference call/response pairs.
tags list[str] [] Free-form classifiers.
deprecated bool False Hide from selectors when True.

Note

ToolSpec is exposed lazily via stargraph.tools.__getattr__ to break a circular import: stargraph.ir._models already imports the enums from stargraph.tools.spec.

SideEffects

stargraph.tools.SideEffects is a StrEnum (FR-33, design 3.4.2). Members are plain lowercase strings on the wire.

Value When to use
none Pure function. No I/O, no globals, no clock. Safe to re-execute infinitely.
read Reads external state but does not mutate (filesystem read, HTTP GET, vector lookup, broker query).
write Mutates state owned by Stargraph (Checkpointer, store write, artifact write).
external Mutates state owned by a third party (HTTP POST, MCP tool call, LLM completion).

The classifier feeds the runtime's replay router and the cost/risk surface that Bosun packs reason about.

ReplayPolicy

stargraph.tools.ReplayPolicy (FR-33, FR-21, NFR-8) is a StrEnum with kebab-cased values.

Value Semantics
must-stub Replay must hit a recorded cassette. Re-running mutates the world; not stubbing is a hard error.
fail-loud No cassette exists and re-execution is disallowed. Replay raises so the operator sees the gap.
recorded-result Prefer the cassette but fall back to re-execution if absent (only safe for none/read).

Default policy mapping

When a tool author omits replay_policy, the decorator derives it from side_effects:

side_effects Default replay_policy
none recorded-result
read recorded-result
write must-stub
external must-stub

Override only when you have a deterministic external call (rare) or want to trade availability for safety on a read path.

@tool decorator

stargraph.tools.tool binds a callable to a ToolSpec. The wrapped callable keeps its original calling convention (sync or async, positional or keyword) and gains a wrapper.spec attribute (Open Q8 resolution: callable wrapper, not a descriptor).

from stargraph.tools import SideEffects, tool

@tool(
    name="broker_request",
    namespace="nautilus",
    version="1",
    side_effects=SideEffects.read,
    requires_capability="tools:broker_request",
)
async def broker_request(*, agent_id: str, intent: str) -> dict:
    ...

Signature

def tool(
    *,
    name: str,
    namespace: str,
    version: str,
    side_effects: SideEffects,
    replay_policy: ReplayPolicy | None = None,
    requires_capability: str | list[str] | None = None,
    input_schema: dict[str, Any] | None = None,
    output_schema: dict[str, Any] | None = None,
    description: str | None = None,
    idempotency_key: str | None = None,
    cost_estimate: Decimal | None = None,
) -> Callable[[Callable[..., Any]], Callable[..., Any]]: ...

Auto schema derivation

When input_schema is omitted, the decorator builds one with pydantic.create_model over the wrapped callable's annotated parameters and takes the JSON Schema via pydantic.TypeAdapter. *args and **kwargs are skipped (they are not part of the schema surface).

When output_schema is omitted, the decorator runs TypeAdapter over the return annotation. A bare None return becomes {"type": "null"}; a missing annotation becomes {} (any value).

Warning

Annotated[T, metadata] filtering, positional-only parameters, and BaseModel-typed input_schema are tracked as TODOs (task 1.13). For those cases, pass input_schema= / output_schema= explicitly.

requires_capability

A single string is normalised to a one-element list and stored on ToolSpec.permissions. None becomes []. The runtime consults the capability gate (stargraph.security.Capabilities.check) before invocation; unauthorised calls raise CapabilityError and never reach the underlying callable.

Wiring a tool into IR

Tools enter the IR via the plugin manifest entry-point group stargraph.tools (see Plugin manifest). The graph YAML references the tool by its registry key:

graph: nautilus_demo
nodes:
  - id: ask_broker
    kind: tool
    tool: nautilus.broker_request@1
    inputs:
      agent_id: "agent-42"
      intent: "{{state.user_intent}}"
    out: broker_reply

The runtime resolves nautilus.broker_request@1 against the registry, runs the capability gate, validates state-derived inputs against ToolSpec.input_schema, and routes through the replay layer per ToolSpec.replay_policy.

Nautilus broker tool

stargraph.tools.nautilus ships one in-tree registry-discoverable tool:

Tool Module Side-effects Required capability
nautilus.broker_request@1 stargraph.tools.nautilus.broker_request read tools:broker_request

broker_request(*, agent_id: str, intent: str) -> dict resolves the lifespan-singleton nautilus.Broker via stargraph.serve.contextvars.current_broker, calls Broker.arequest, and returns BrokerResponse.model_dump(mode="json") plus a __stargraph_provenance__ envelope:

{
  "...": "broker response fields",
  "__stargraph_provenance__": {
    "origin": "tool",
    "source": "nautilus",
    "external_id": "<broker request_id>"
  }
}

The envelope is identical to the one BrokerNode writes, so consumers that pattern-match on the bundle work the same against either form.

Example

Use BrokerNode when the broker call is a fixed slot in a graph spec. Use nautilus.broker_request@1 (this tool) inside a ReAct skill or sub-graph dispatcher where the call site is dynamic.

Raises StargraphRuntimeError if no Broker is registered (lifespan factory did not run, or nautilus.yaml was missing at startup).

See also

  • Plugin manifest — entry-point group stargraph.tools.
  • IR Schema — wire format for ToolSpec.
  • Adapters — DSPy and MCP seams that bind external callables as Stargraph tools/nodes.