Sidecar Python tools
A Pydantic agent can point at a sidecar Python module of deterministic functions, and the model calls those functions as tools. Use this when work is rote — data transforms, scoring, matching, pagination loops, ETL — so the function runs in Python at no token cost and the model just supervises which function to call. It’s the cost-and-speed lever versus making the model do everything through MCP/Composio tool calls.
Declaring a tools module
Section titled “Declaring a tools module”Add tools_module: to the agent spec, pointing at a .py file that sits next
to the spec:
name: revenue-rollupmodel: anthropic:claude-sonnet-5tools_module: revenue_tools.py # agents/pydantic-agentspec/revenue_tools.pyinstructions: | Call summarize_arr to compute the monthly ARR waterfall, then post a one-paragraph summary. Don't recompute anything yourself.Rules:
tools_moduleis a bare filename (no/, no..), resolved in the same directory as the spec, and must end in.py.- The module must define a top-level
tools = [...]list of the functions to expose. Anything not in that list stays private. - The model sees each function’s signature + docstring — so type the parameters and write a clear one-line docstring. That text becomes the tool’s schema.
- A declared module that’s missing from the repo, or whose
toolslist is empty or non-callable, fails the run loudly — it never silently runs without the tools. - Tool calls appear in the run detail and the Tool uses view by function name, just like MCP tools.
Authentication flows through Connections
Section titled “Authentication flows through Connections”Tools never carry a hardcoded key. When a function needs to reach an external
system, import the bundled tas_tools helper and ask for a
connection the agent already declares. This is
supported for Native MCP connections — their OAuth token also works against
the provider’s REST API:
import httpximport tas_tools
def list_companies() -> list[dict]: """Return all Attio companies.""" c = tas_tools.connection("attio") # the agent's attio connection r = httpx.get( "https://api.attio.com/v2/objects/companies/records/query", headers={"Authorization": f"Bearer {c.access_token}"}, ) r.raise_for_status() return r.json()["data"]
def summarize_arr(records: list[dict]) -> dict: """Compute the monthly ARR waterfall from company records.""" ...
tools = [list_companies, summarize_arr]tas_tools.connection(provider, name="default") returns an object with
.access_token and .mcp_url, or raises a clear error if that connection isn’t
active for the run.
For a service that uses a plain API key (no OAuth) — like Clay — read a workspace Secret instead:
import httpximport tas_tools
def enrich(domain: str) -> dict: """Enrich a company domain via Clay.""" key = tas_tools.secret("clay") # set under Connections → Secrets r = httpx.post( "https://api.clay.com/v1/enrich", headers={"Authorization": f"Bearer {key}"}, json={"domain": domain}, ) r.raise_for_status() return r.json()
tools = [enrich]tas_tools.secret("name") returns the value an admin stored under Connections →
Secrets, or raises a clear error if it isn’t set. Secrets are only injected into
runs that have a tools module.
Dependencies
Section titled “Dependencies”The standard library, httpx, and pydantic are available out of the box. For
other third-party packages (pandas, database drivers, cloud SDKs), add a pinned
line to api/scripts/requirements-tools.txt in your TAS deployment and redeploy
— they’re installed into the wrapper’s Python environment at image build, so a
given image stays reproducible.
When to reach for this
Section titled “When to reach for this”| Use a sidecar tool when… | Use an MCP/Composio tool when… |
|---|---|
| The work is deterministic (math, transforms, ETL) | You need a service’s managed action surface |
| You want it off the token budget / faster | The action is inherently a model decision |
| You can express it as a small Python function | The provider already exposes the tool you need |