Agents & Tools
ThinkLang's agentic runtime is fully available from the library. Define tools that the LLM can call, then run an agent loop that orchestrates tool use automatically.
Defining Tools
Use defineTool() to create tools. Accepts Zod schemas or raw JSON Schema for input.
typescript
import { defineTool } from "thinklang";
import { z } from "zod";
const searchDocs = defineTool({
name: "searchDocs",
description: "Search internal documentation for relevant info",
input: z.object({ query: z.string() }),
execute: async ({ query }) => {
const results = await docsIndex.search(query);
return results.map(r => r.title).join("\n");
},
});Running an Agent
Use agent() to start an agentic loop. The LLM calls tools as needed until it produces a final answer.
typescript
import { agent, defineTool, zodSchema } from "thinklang";
import { z } from "zod";
const getWeather = defineTool({
name: "getWeather",
description: "Get weather for a city",
input: z.object({ city: z.string() }),
execute: async ({ city }) => {
const res = await fetch(`https://api.weather.example/v1/${city}`);
return res.text();
},
});
const Report = z.object({
city: z.string(),
temperature: z.number(),
conditions: z.string(),
recommendation: z.string(),
});
const result = await agent<z.infer<typeof Report>>({
prompt: "What is the weather in Tokyo? Recommend what to wear.",
tools: [getWeather],
...zodSchema(Report),
maxTurns: 5,
});
console.log(result.data); // the Report object
console.log(result.turns); // how many loop iterations
console.log(result.toolCallHistory); // full history of tool callsAgent Options
| Option | Type | Default | Description |
|---|---|---|---|
prompt | string | required | The goal for the agent |
tools | Tool[] | required | Tools the agent can call |
jsonSchema | object | — | JSON Schema for the final output |
maxTurns | number | 10 | Maximum loop iterations |
guards | GuardRule[] | — | Validate the final output |
retryCount | number | — | Retry the entire loop on failure |
fallback | () => T | — | Fallback if all retries fail |
onToolCall | (call) => void | — | Called before each tool executes |
onToolResult | (result) => void | — | Called after each tool executes |
abortSignal | AbortSignal | — | Cancel the agent loop |
context | object | — | Context data for the agent |
model | string | — | Override the default model |
How the Loop Works
- The prompt is sent to the LLM along with tool definitions.
- The LLM either calls one or more tools or returns a final answer.
- If tools were called, their results are fed back to the LLM.
- Steps 2-3 repeat until the LLM produces a final answer or the turn limit is reached.
- The final answer is parsed into the specified type and returned.
- Throws
AgentMaxTurnsErrorif the turn limit is reached without a final answer.
Built-in Tools
ThinkLang ships with opt-in built-in tools:
typescript
import { agent, fetchUrl, readFile, writeFile, runCommand } from "thinklang";
const result = await agent({
prompt: "Read the README and summarize it",
tools: [readFile],
jsonSchema: { type: "string" },
maxTurns: 3,
});| Tool | Description |
|---|---|
fetchUrl | Fetch a URL via HTTP GET |
readFile | Read a local file |
writeFile | Write content to a local file |
runCommand | Run a shell command |
Observability Hooks
Track what the agent is doing in real time:
typescript
const result = await agent({
prompt: "Research this topic",
tools: [searchDocs],
jsonSchema: { type: "string" },
onToolCall: (call) => {
console.log(`Calling tool: ${call.name}`, call.input);
},
onToolResult: (result) => {
console.log(`Tool ${result.toolName}:`, result.isError ? "ERROR" : "OK");
},
});Cancellation
Use AbortSignal to cancel a running agent:
typescript
const controller = new AbortController();
setTimeout(() => controller.abort(), 30000); // 30s timeout
const result = await agent({
prompt: "Complex research task",
tools: [searchDocs],
jsonSchema: { type: "string" },
abortSignal: controller.signal,
});Multi-Agent Orchestration
A coordinator agent can delegate to specialized sub-agents. Each sub-agent runs its own agentic loop with its own tools, prompt, and turn limit.
typescript
import { agent, defineTool, type SubAgent } from "thinklang";
import { z } from "zod";
const searchWeb = defineTool({
name: "searchWeb",
description: "Search the web for information",
input: z.object({ query: z.string() }),
execute: async ({ query }) => `Results for: ${query}`,
});
// Define sub-agents
const researcher: SubAgent = {
name: "researcher",
description: "Research a topic thoroughly using web search",
prompt: "You are a research assistant. Use the search tool to find relevant information.",
tools: [searchWeb],
maxTurns: 3,
};
const writer: SubAgent = {
name: "writer",
description: "Write well-structured content based on research findings",
prompt: "You are a technical writer. Create clear, well-organized content.",
};
// Run the coordinator
const result = await agent<string>({
prompt: "Create a brief report on TypeScript trends",
tools: [],
agents: [researcher, writer],
maxTurns: 10,
jsonSchema: { type: "string" },
onAgentCall: (name, input) => console.log(`→ Delegating to ${name}`),
onAgentResult: (name) => console.log(`← ${name} completed`),
});
console.log(result.data);How It Works
- Sub-agents are automatically wrapped as tools that the coordinator can call.
- When the coordinator calls a sub-agent, a nested agentic loop runs with the sub-agent's configuration.
- The sub-agent's result is returned to the coordinator as a tool result.
- Token usage from all sub-agents is aggregated into the coordinator's
totalUsage.
SubAgent Options
| Option | Type | Default | Description |
|---|---|---|---|
name | string | required | Name used to identify the sub-agent |
description | string | required | Description shown to the coordinator |
prompt | string | — | System prompt for the sub-agent |
tools | Tool[] | — | Tools the sub-agent can use |
maxTurns | number | 10 | Maximum turns for the sub-agent's loop |
model | string | — | Override the model for this sub-agent |
jsonSchema | object | — | JSON Schema for the sub-agent's output |
ThinkLang Language Syntax
In .tl files, you can declare sub-agents and use them with the with agents: clause:
agent researcher("Research topics") with tools: search
agent writer("Write content")
let report = agent<Report>("Create a report")
with tools: search
with agents: researcher, writer
max turns: 10Next Steps
- Core Functions for think, infer, reason
- Big Data & Streaming for batch processing
- Error Handling for AgentMaxTurnsError and ToolExecutionError
- Agents & Tools (Language Guide) for the .tl syntax equivalent