Autonomous Agent
An autonomous agent plans its own work, executes it, and critiques its results before responding. This guide builds a deep-research agent end-to-end: it generates search queries, fans them out in parallel, optionally fetches primary sources, renders a Jinja prompt over the aggregate, and then self-checks the final answer.
Because every side effect is a logged host function call that the runtime caches and replays, the entire run is a replayable checkpoint — iterate on the prompt with zero LLM calls.
The full agent
agents/deep_researcher.ts
import type { Chidori } from "chidori";
export async function agent(
input: { question: string; depth?: "standard" | "deep" },
chidori: Chidori,
) {
const { question } = input;
const depth = input.depth ?? "standard";
// 1. Plan — ask the LLM what to search for
const queries: string[] = await chidori.prompt(
"Generate 3 search queries for: " + question + "\nReturn as a JSON array of strings.",
{ type: "json" },
);
// 2. Execute — fan out searches in parallel
const allResults = await chidori.parallel(
queries.map((q) => () => chidori.tool("web_search", { query: q })),
);
// 3. Optionally fetch primary sources for deep research
let sources: any[] = [];
if (depth === "deep") {
const urls = allResults.flatMap((batch) => batch.slice(0, 2).map((r) => r.url));
sources = await chidori.parallel(
urls.map((u) => () => chidori.tool("fetch_url", { url: u })),
);
}
// 4. Synthesize — render a Jinja prompt over the gathered context
const answer = await chidori.prompt(
await chidori.template("prompts/research.jinja", {
question,
results: allResults,
sources,
}),
{ model: "claude-opus", temperature: 0.3, type: "draft" },
);
// 5. Self-critique — fact-check the answer before returning it
const final = await chidori.prompt(
"Fact-check this answer. Return the corrected version.\n\n" + answer,
{ type: "final" },
);
return {
answer: final,
sources: allResults,
queriesUsed: queries,
};
}The supporting pieces
Tools
tools/search.ts
import type { Chidori, ToolDefinition } from "chidori";
export const tool: ToolDefinition = {
name: "web_search",
description: "Search the web and return results.",
parameters: {
type: "object",
properties: { query: { type: "string" }, maxResults: { type: "number" } },
required: ["query"],
},
};
export async function run(args: { query: string; maxResults?: number }, chidori: Chidori) {
const resp = await chidori.http("https://api.search.example/search", {
query: { q: args.query, limit: args.maxResults ?? 5 },
});
return resp.body.results;
}tools/fetch.ts
import type { Chidori, ToolDefinition } from "chidori";
export const tool: ToolDefinition = {
name: "fetch_url",
description: "Fetch a URL and return its text content.",
parameters: {
type: "object",
properties: { url: { type: "string" } },
required: ["url"],
},
};
export async function run(args: { url: string }, chidori: Chidori) {
const resp = await chidori.http(args.url);
return { url: args.url, text: resp.body };
}Prompt template
prompts/research.jinja
You are a senior research analyst. Answer the question using the evidence below.
Cite specific sources with [index] markers. If the evidence is insufficient, say so.
## Question
{{ question }}
## Search results
{% for batch in results %}
{% for r in batch %}
- [{{ loop.index }}] {{ r.title }} ({{ r.url }}): {{ r.snippet }}
{% endfor %}
{% endfor %}
{% if sources %}
## Primary sources
{% for s in sources %}
### {{ s.url }}
{{ s.text[:2000] }}
{% endfor %}
{% endif %}
## AnswerRun it
chidori run agents/deep_researcher.ts \
--input question="How does Rust's borrow checker work?" \
--input depth=deep \
--traceWhat's autonomous about this
- The LLM plans — queries aren't hardcoded; the agent asks the model what to search for.
- The agent routes its own work —
depth="deep"branches into primary-source fetching;depth="standard"skips it. - It critiques itself — the final step is a fact-check pass over its own draft.
- It explains its reasoning —
queriesUsedis returned alongside the answer so a reviewer can audit the plan.
Iterating safely
The expensive parts of this agent are the LLM calls and the web searches. Thanks to checkpointing, you can iterate on the prompt template or the critique step without paying for those again — replaying a session makes zero LLM calls and produces identical output:
# First run — real searches, real LLM calls; capture the session
chidori run agents/deep_researcher.ts --input question="..." --trace --replay session.json
# Replay the saved session while iterating — no network, no spend
chidori run agents/deep_researcher.ts --replay session.jsonWhere to go next
- Add a human-in-the-loop approval step with
chidori.input()before returning the answer. - Expose the agent as a webhook with
chidori serveso it can be triggered from Slack. - Store question-answer pairs in
chidori.memory()so future runs can reference prior research.
