Investment Committee

Three specialist agents (Fundamental Analyst, Risk Officer, and Committee Chair) evaluate a live NVDA equity position. The Analyst and Risk Officer run concurrently as isolated actors and return typed verdicts via mailbox messaging. The Chair waits for both, synthesizes them into a final decision, and persists the memo.

This example demonstrates:

  • Concurrent specialist agents via spawn_link and mailbox-based result collection
  • Live REST calls to Yahoo Finance to ground the analysis in real data
  • confidence-gated fallbacks on every agent decision
  • Institutional memory across sessions via remember and recall
investment_committee.tn
// Turn Language: Investment Committee
// Concurrent specialist agents with mailbox-based result collection.

struct MarketSnapshot    { ticker: Str, price: Num, pe_ratio: Num };
struct FundamentalThesis { valuation_signal: Str, conviction: Num, thesis: Str };
struct RiskVerdict       { breach: Bool, approved_usd: Num, risk_note: Str };
struct CommitteeMemo     { decision: Str, approved: Num, summary: Str, flags: Str };

let net = use "std/net";

let fetch_market_data = turn(ticker: Str) -> MarketSnapshot {
  let id   = grant identity::network("public");
  let raw  = net.get({
      "url":      "https://query1.finance.yahoo.com/v8/finance/chart/" + ticker + "?interval=1d&range=1d",
      "identity": id
  });
  context.append("Yahoo Finance feed: " + raw);
  let snap = infer MarketSnapshot {
      "Parse the Yahoo Finance JSON for " + ticker + ". Return price and pe_ratio.";
  };
  return snap;
};

turn {
  call("echo", "=== INVESTMENT COMMITTEE START ===");

  let ticker    = "NVDA";
  let requested = 2000000.0;
  let parent    = self;

  let snap = fetch_market_data(ticker);

  // Spawn specialist agents concurrently.
  // Each actor has its own isolated context window and returns via mailbox.
  let analyst_pid = spawn_link turn() {
      call("echo", "[Analyst] Reviewing " + snap.ticker + "...");
      let prior = recall("investment_memo_" + snap.ticker);
      if prior != null {
          context.append("Prior notes: " + prior);
      }
      let thesis = infer FundamentalThesis {
          "Senior equity analyst thesis for " + snap.ticker + ". Price: " + snap.price + " PE: " + snap.pe_ratio;
      };
      if confidence thesis < 0.7 {
          let fallback = FundamentalThesis {
              valuation_signal: "FAIR",
              conviction: 4.0,
              thesis: "Low confidence. Reverting to neutral fair value."
          };
          send parent, { "from": "analyst", "result": fallback };
          return null;
      }
      remember("investment_memo_" + snap.ticker, thesis.thesis);
      send parent, { "from": "analyst", "result": thesis };
  };

  let risk_pid = spawn_link turn() {
      call("echo", "[Risk Officer] Reviewing exposure for " + snap.ticker + "...");
      context.system("You are the Chief Risk Officer. The exposure limit is $5M.");
      let verdict = infer RiskVerdict {
          "Review the asset and determine if risk limits are breached for a $" + requested + " position.";
      };
      send parent, { "from": "risk", "result": verdict };
  };

  // Collect results from both agents via mailbox.
  // No sleep, no polling. The VM yields until both messages arrive.
  let msg1 = receive;
  let msg2 = receive;

  let thesis  = msg1["result"];
  let risk    = msg2["result"];

  call("echo", "[Chair] Synthesizing final decision...");

  context.append("Analyst Thesis: " + thesis["thesis"] + " Conviction: " + thesis["conviction"]);
  context.append("Risk verdict: breach=" + risk["breach"] + " note: " + risk["risk_note"]);

  let memo = infer CommitteeMemo {
      "Synthesize the final binding committee decision for " + ticker + ".";
  };

  let session = recall("committee_session_" + ticker);
  if session == null { session = 1; } else { session = session + 1; }
  remember("committee_session_" + ticker, session);
  remember("committee_history_" + ticker, "Session " + session + ": " + memo.decision);

  call("echo", "Decision: " + memo.decision);
  call("echo", "Approved Amount: $" + memo.approved);
  call("echo", "Summary: " + memo.summary);
  return memo;
}

How It Works

The Chair first fetches live Yahoo Finance data to ground the committee's reasoning in real market prices. It then spawns two specialist actors: the Fundamental Analyst and the Risk Officer. Each runs independently with its own context window and returns its typed verdict via send parent, ....

The Chair calls receive twice to collect both verdicts from its mailbox. The VM yields until each message arrives, so there is no sleep or polling. The Chair then enriches its context with both verdicts and infers the final binding CommitteeMemo.

Session counts and decision history are persisted via remember so subsequent runs can access prior committee decisions.

Running It

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

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


Next Steps