Skip to content

Tools

sectr.tools is the framework-free tool story: plain functions become tools with a JSON schema derived from their signature, and approval gating is evaluated locally — no runtime round-trip to the platform.

from sectr.tools import tool
@tool
async def lookup_order(order_id: str) -> dict[str, str]:
"""Look up the status of an order."""
return {"order_id": order_id, "status": "shipped"}
@tool(needs_approval=True)
async def refund(order_id: str) -> str:
"""Refund an order (requires manager approval)."""
return f"Refunded {order_id}"

(Snippet: examples/raw/tools.py — the conformance fixture.)

  • name — from the function name (@tool(name="…") to override).
  • description — from the docstring (description=… to override).
  • openai_schema — a chat-completions tools=[…] entry whose parameters JSON schema is derived from the signature’s type annotations (un-annotated parameters accept any JSON; parameters with defaults are optional). Feed it to your provider’s tool list: [t.openai_schema for t in TOOLS].
  • Calling a Tool awaits the function if it’s async.

A tool is gated when either applies:

  1. Code marking — @tool(needs_approval=True).
  2. Platform policy — the deployment config (SECTR_APPROVAL_POLICY, a JSON list of tool names the sidecar injects) can gate a tool the author forgot to mark. This is what makes “refund requires approval” enforceable without re-deploying code.

A gated call raises sectr.tools.ApprovalRequired, which the adapter converts into the final ToolApprovalRequired event — the turn suspends. See approvals for the full flow, and SessionContext.execute for how the decision returns.

is_gated(name) and get_tool(name) are the lookup half of the registry — mostly adapter machinery, exposed for completeness.