Tools, and binding them to a model
The twice-daily todoist-hygiene job runs a prompted skill today. This module gives it a real agent brain: tools wrapping your existing scripts/todoist/client.py, emitting a plan that validates against the apply contract you already trust. Same safety, sharper filing.
A tool is a function the model can decide to call. You describe it, name, description, argument schema, and hand it to the model. The model, mid-generation, can emit a request to call it with arguments; your runtime executes it and feeds the result back.
import { tool } from "langchain";
import * as z from "zod/v4";
const snapshotInbox = tool(
async (input) => {
// shell out to scripts/todoist/client.py snapshot, return JSON string
return await runTodoist("snapshot", input.date);
},
{
name: "snapshot_inbox",
description: "Fetch a snapshot of the Todoist inbox for a given date.",
schema: z.object({ date: z.string() }),
},
);const withTools = model.bindTools([snapshotInbox]);
const res = await withTools.invoke(messages);
// res.tool_calls may contain requests you must execute, then loop backThat is the raw level: attach tools, the model gains the ability to request them, and the resulting tool_calls are requests you must execute yourself and feed back as tool messages.
Try it yourself
What a tool needs
These are the three the model itself depends on. The function body is not one of them.
What three things does tool(fn, {...}) require in its options?
Reveal answer
A name, a description (so the model knows when to call it), and an argument schema (usually zod), plus the function itself that actually runs.