Agent Loop docs
Tools and approval
A local tool combines a handwritten JSON Schema with a Python handler. The schema is what
the model sees; @tool does not infer it from the function signature. These examples use
OpenAI, configured with LLM_PROVIDER=openai and OPENAI_API_KEY as in
Hello World.
Run a local tool
Define the schema, decorate the function, and pass the resulting tool to Agent:
import os
from agentloop import Agent, tool
ADD_SCHEMA = {
"type": "object",
"properties": {
"left": {"type": "integer"},
"right": {"type": "integer"},
},
"required": ["left", "right"],
"additionalProperties": False,
}
@tool(parameters=ADD_SCHEMA)
def add(left: int, right: int) -> str:
"""Add two integers."""
return str(left + right)
with Agent.from_env(
os.environ,
tools=[add],
system="Use the add tool for arithmetic before answering.",
) as agent:
result = agent.run_result("What is 27 plus 15? Call add to calculate it.")
print(result.text)
print(result.summary.tool_calls)
Output:
27 plus 15 is 42.
{'add': 1}
result.summary.tool_calls counts calls by tool name; the full call and result are in
result.transcript.
Dependencies and approval
Declare a keyword-only deps parameter when a tool needs trusted application state. A
before_tool hook can deny a call before its handler runs:
import os
from agentloop import Agent, Deps, tool
from agentloop.transcript import ToolCallPart
class NoteDeps(Deps):
notes: dict[str, str]
def approve(call: ToolCallPart) -> str | None:
if call.arguments.get("name") == "private":
return "private notes are unavailable"
return None
@tool(parameters={
"type": "object",
"properties": {"name": {"type": "string"}},
"required": ["name"],
"additionalProperties": False,
})
def read_note(name: str, *, deps: NoteDeps) -> str:
"""Read a note by name."""
return deps.notes.get(name, "Note not found")
deps = NoteDeps(
notes={"public": "Blue is a color.", "private": "Hidden text."},
before_tool=approve,
)
with Agent.from_env(
os.environ,
tools=[read_note],
deps=deps,
system=(
"Call read_note once for each request. Use the name between backticks exactly "
"as written. If denied, explain the denial."
),
) as agent:
public = agent.run_result("Read the note named `public`.")
print(public.text)
print(public.summary.tool_calls, public.summary.tool_errors)
private = agent.run_result("Read the note named `private`.")
print(private.text)
print(private.summary.tool_calls, private.summary.tool_errors)
Output:
Blue is a color.
{'read_note': 1} {}
I’m not allowed to access notes marked as private, so I can’t read or display the contents of the note named `private`.
{'read_note': 1} {'read_note': 1}
The notes dictionary belongs to the application; the model sees only the name schema.
For the private request, the approval hook returns a reason and read_note does not run.
The denied call still appears in tool_calls and tool_errors.
Independent tool calls in one model response run concurrently, up to eight at a time, and
their results retain the model's call order. Set parallel_safe=False on @tool if a
handler cannot run alongside others. Approval hooks may run on worker threads, so shared
state they access must be thread-safe.
See the tooling API.