Web Search Agent

An autonomous research agent that combines HTTP tool calls, the actor model, and structured LLM inference to perform real multi-source research.

A planner LLM formulates three independent search queries. Three actor processes execute those queries concurrently against Wikipedia. Their results are collected via mailbox messaging and synthesized into a structured research report.

deep_research.tn
struct SearchPlan {
  rationale: Str,
  query_1: Str,
  query_2: Str,
  query_3: Str
};

struct ResearchReport {
  title: Str,
  executive_summary: Str,
  key_findings: Str,
  conclusion: Str,
  confidence_score: Num
};

let net  = use "std/net";
let json = use "std/json";

let search_wikipedia = turn(query: Str) -> Str {
  call("echo", "[Search] Executing: " + query);
  let endpoint = "https://en.wikipedia.org/w/api.php?action=query&list=search&srsearch=" + query + "&utf8=&format=json";
  try {
      let id      = grant identity::network("public");
      let raw     = net.get({ "url": endpoint, "identity": id });
      let parsed  = json.parse(raw);
      let results = parsed["query"]["search"];
      if results == null {
          return "No results found for: " + query;
      }
      return results[0]["snippet"] + " " + results[1]["snippet"];
  } catch(e) {
      return "Search failed: " + e;
  }
};

context.system("You are an expert autonomous research planner.");

let plan = infer SearchPlan {
  "The user wants to know about: artificial intelligence in medicine.
   Break this down into exactly 3 highly specific search terms.
   URL-encode each query: replace spaces with %20.";
};

let parent = self;

let pid_1 = spawn_link turn() {
  let result = search_wikipedia(plan["query_1"]);
  send parent, result;
};

let pid_2 = spawn_link turn() {
  let result = search_wikipedia(plan["query_2"]);
  send parent, result;
};

let pid_3 = spawn_link turn() {
  let result = search_wikipedia(plan["query_3"]);
  send parent, result;
};

// Collect results from all three actors via mailbox.
// receive blocks until a message arrives. No polling needed.
let data_1 = receive;
let data_2 = receive;
let data_3 = receive;

context.append(data_1);
context.append(data_2);
context.append(data_3);

let report = infer ResearchReport {
  "Review the search snippets in your context window.
   Synthesize a comprehensive, objective research report.
   Rate your confidence score between 0.0 and 1.0 based on data quality.";
};

call("echo", "Title:      " + report["title"]);
call("echo", "Summary:    " + report["executive_summary"]);
call("echo", "Confidence: " + report["confidence_score"]);

How It Works

The program follows a plan/dispatch/gather structure.

First, a planner infer call breaks the research topic into three specific search queries. This separates the reasoning work (what to search for) from the execution work (how to fetch it).

Three actors are spawned concurrently, each running an HTTP search against Wikipedia. Because they are isolated processes with no shared state, they run in parallel. Each sends its result back to the parent process via send parent, result.

The parent collects all three results with three receive calls. The VM yields until each message arrives, so no polling or sleep is needed. The results are appended to the context window and a final infer synthesizes the full report from the accumulated evidence.

Running It

export TURN_LLM_PROVIDER=openai
export OPENAI_API_KEY=sk-...
turn run impl/examples/deep_research.tn

The full source is in impl/examples/deep_research.tn.


Next Steps