For the complete documentation index, see llms.txt. This page is also available as Markdown.

Code Tools

Write a Python function that runs as a tool, and can call other tools

Code tools let you write Python that runs as a tool inside your agent, without leaving Kiln. They're stored in your project like any other artifact, and appear in the tools dropdown alongside your MCP servers and search tools.

Why Code Tools

Agents rarely fail because the model is bad at reasoning. They fail because the interface they're given is messy:

  • N+1 tool loops: an API with no batch endpoint forces the agent to call get_user fifty times, one per turn, burning context and time.

  • Context floods: a tool returns a 40KB JSON blob when the agent needed three fields from it.

  • Missing operations: the thing the agent actually needs is two API calls and a join, and no single tool does it.

You can try to fix these in the prompt, and the model will get it right most of the time. A code tool fixes them in code, once, and gets it right every time. A batch wrapper, a result filter, or a purpose-built endpoint is a durable artifact — it doesn't drift when you change models.

Code tools run inside the Kiln desktop app, on your machine. They aren't available to server-side deployments.

Creating a Code Tool

Open "Tools" > "Add Tools" > "Code Tool", and Kiln will walk you through two steps.

Step 1: Define the Tool

  • Display Name: the name you and your team see in Kiln. For example "User Lookup".

  • Tool Name: the function name exposed to the model. Lowercase with underscores, for example get_user.

  • Description: shown to the model. Describe what the tool does and when to use it — this is what the model reads when deciding whether to call it.

  • Parameters: the arguments the model passes to your tool, defined with the same schema builder used for task inputs and outputs.

Step 2: Write the Code

Write a function named run. Your parameters arrive as keyword arguments matching the schema you defined:

Both sync and async forms work — use async def run(...) when you want concurrency.

Return values become the tool output the model sees. Strings pass through as-is; dicts, lists, numbers and booleans are JSON-serialized for you.

The right-hand panel of the editor has two things worth using before you save:

  • Tool Access: an allowlist of the tools your code may call. Code with nothing selected can't make tool calls at all.

  • Test panel: run your tool against real arguments, calling your real allowlisted tools, and see what comes back.

Under "Advanced Options" you can set the timeout: the wall-clock limit for one invocation, including any nested tool calls. Defaults to 60 seconds.

Calling Other Tools

The most powerful thing a code tool can do is call other tools. Two modules are available in your code:

Call an allowlisted tool as an attribute, with keyword arguments:

A few rules worth knowing up front:

  • Tool calls always return a string — byte for byte what the model would have seen. Parse it yourself with json.loads when the tool returns JSON.

  • Keyword arguments only. Positional arguments raise an error.

  • Only allowlisted tools resolve. Calling anything else raises ToolNotAllowed, and the error lists what is available.

  • tools.list_tools() returns the tools in your allowlist, with their descriptions and parameter schemas.

For true concurrency, use async_tools with asyncio.gather — this is the fix for the N+1 loop:

Tool calls raise typed exceptions you can catch for retries: ToolNotAllowed, ToolTimeout, and ToolCallError. Import them from kiln.tools or kiln.async_tools.

Code tools can call other code tools — they're just tools.

Code Trust

The trust gate is the security boundary. Adding or editing code in a project requires confirming that you trust it; running code you've already saved and trusted doesn't prompt you again. Trust is granted for the session, so Kiln asks again after a restart.

This matters most when a project came from somewhere else. Kiln projects are designed to be shared across teams with Git, and a project you sync or import may contain code tools written by someone else. Importing a project authored elsewhere requires explicit approval before its code will run — treat that prompt the way you'd treat running an unfamiliar script from the internet, and read the code first.

Your Code is a Real Python File

Kiln stores your tool's source as tool.py, in the tool's folder next to code_tool.kiln:

Because it's a plain Python file rather than a string inside JSON, it's importable, lintable, type-checkable, and produces a readable diff in Git.

Testing with pytest

Kiln ships a pytest plugin, so you can write standard tests against your tool in a normal Python environment. Install kiln_ai (pip install kiln-ai) and the plugin is auto-discovered: the from kiln import tools at the top of your tool.py resolves under pytest, and a kiln_tools fixture becomes available for stubbing tool responses.

Create test_tool.py in the same folder as tool.py:

The fixture stubs replies with kiln_tools.set(name, reply), forces errors with kiln_tools.set_error(name, exc), and records every call in kiln_tools.calls. It behaves like the real runtime: unregistered tools raise ToolNotAllowed, positional arguments raise ToolCallError.

Kiln doesn't store, display, or run these tests — the loop lives in your own Python environment.

tool.py is a fixed filename, so running pytest across several tool folders at once hits a module name collision. Run pytest from inside a single tool folder, or use pytest --import-mode=importlib.

Code Tools vs Other Options

Code Tool
MCP Server
Kiln Task as Tool

Best for

Wrapping, batching and filtering existing tools

Connecting an existing service or third-party integration

Delegating a sub-problem to another agent

Written in

Python, inside Kiln

Any language, outside Kiln

A Kiln task prompt

Deterministic

Yes

Depends on the server

No — it's a model call

Setup

Low

Medium

Medium

Learn More

Last updated