Writing tools
The agent can call custom tools during a conversation. Two places register them, and both are
pi extensions: a module that calls pi.registerTool(...) with a name, a description, a
Typebox parameter schema, and an execute function.
- Platform extensions in
src/pi-agent/extensions/: charts, tables, forms, and memory. Pi loads every file passed via--extension. - Core agents in
src/pi-agent/core-agents/:google(Gmail and Sheets),hubspot,reports, andedb-modeller. They load on the parent pi process (adrs/0004.md, adrs/0005.md).
How a tool returns results
A tool result has two channels:
content— text that goes back into the model's context.details— data handed to the frontend for rendering and state, and never sent to the model.
This split is why charts and forms can appear in the UI without their raw rows passing through the conversation.
The patterns
- Vendor HTTP via the nono proxy — HubSpot tools (
hubspot_whoami,hubspot_list,hubspot_create,hubspot_update,hubspot_owners,hubspot_create_task) and the Google tools (send_email,fetch_sheet,search_spreadsheets) call{VENDOR}_BASE_URLwith a phantom{VENDOR}_API_KEY. nono injects the real token; Django is not on the hot path. See HubSpot Tools and adding a vendor. - Deterministic rendering —
render_chart,render_table, andrender_formcall no model and no network. They validate and shape data into the frontend's json-render components and hand it back on thedetailschannel.rasterize_charttakes the same chart arguments and produces a PNG, for sending a chart outside the chat, for example by email. - Memory access —
fetch_memoryreads rows that a tool staged inchat_memoryentry, so large tables are pulled into context only when the agent asks, not on every fetch. The model and the API were renamed off their old MCP names (adrs/0006.md).
Adding a tool
Drop a new .ts file in src/pi-agent/extensions/ that default-exports a
(pi: ExtensionAPI) => void and calls pi.registerTool(...). Register it with --extension
in pi_runtime.py and k8s_runtime.py. Add any shared logic as a plain sibling module (like
memory.ts or chartShape.ts). Pi only turns files passed via --extension into tools, so
imported helpers stay ordinary modules.
A tool that belongs to one platform agent goes in that agent's folder under
src/pi-agent/core-agents/ and is loaded from that agent's index.ts, not as another loose
file in extensions/.