Quickstart

Run your first agent with no API key, then switch to a real model.

You'll build a small weather agent with one tool. First it runs on a scripted model, so it works offline with no keys. Then you swap in a real model.

Set up threads

threads isn't published yet, so run it from a clone. Follow Installation for your language, then come back.

Write a tool and an agent

A tool is a name, a description, an input schema and a function. effect: "read_only" tells threads the tool changes nothing, so it runs without asking for approval.

import { agent, scriptedModel, sqlite, tool } from "@threads/core";
import { z } from "zod";

const getWeather = tool({
  name: "get_weather",
  description: "Get the weather for a city.",
  input: z.object({ city: z.string() }),
  runs: "host",
  effect: "read_only",
  execute: async ({ city }) => `It is sunny in ${city}.`,
});

Script the model

A scripted model plays back fixed responses in order. This one calls the tool, then answers.

const model = scriptedModel({
  responses: [
    {
      content: [{ type: "tool_use", call_id: "c1", name: "get_weather", input: { city: "Paris" } }],
      stop_reason: "tool_use",
      usage: { input_tokens: 20, output_tokens: 5 },
    },
    {
      content: [{ type: "text", text: "It is sunny in Paris." }],
      stop_reason: "end_turn",
      usage: { input_tokens: 40, output_tokens: 8 },
    },
  ],
});

Run it

sqlite(":memory:") keeps the log in memory for this example.

const weather = agent({
  name: "weather",
  instructions: "Answer questions about the weather.",
  model,
  tools: [getWeather],
});

const result = await weather.run("What is the weather in Paris?", { store: sqlite(":memory:") });
if (result.status === "completed") console.log(result.output);
else console.log(result.status);

Run the file with bun index.ts or uv run python main.py. It prints:

It is sunny in Paris.

In Python, an agent with tools needs deps= on every run. Pass deps=None when your tools don't use any. See Agents.

Switch to a real model

Replace the scripted model with a provider. The key comes from ANTHROPIC_API_KEY. The store sqlite(".threads") keeps every run on disk, so you can inspect, resume and fork it later.

import { anthropic } from "@threads/anthropic";

const weather = agent({
  name: "weather",
  instructions: "Answer questions about the weather.",
  model: anthropic({
    model: "claude-sonnet-5",
    maxTokens: 8192,
    contextWindow: 1_000_000,
    maxOutputTokens: 128_000,
  }),
  tools: [getWeather],
});

const result = await weather.run("What is the weather in Paris?", { store: sqlite(".threads") });

switch (result.status) {
  case "completed":
    console.log(result.output);
    break;
  case "parked":
    console.log("waiting on", result.reason);
    break;
  default:
    console.log(result.status);
}

Results are values

A run never throws for an expected outcome. It returns one of these, keyed on status:

statusMeaning
completedThe agent finished. output holds the final text.
parkedThe run is waiting: for an approval, or for a decision about an action that may already have happened. See Human-in-the-loop.
failedThe run stopped with an error code, such as max_turns or model_unavailable.
budget_exhaustedA budget ran out.
cancelledSomeone cancelled the thread.
handed_offThe agent handed the conversation to another agent.

A tool without effect: "read_only" asks for approval before it runs, so the run parks with awaiting_approval. See Permissions & approvals to allow it.

Next steps

Edit on GitHub

On this page