Advanced Start Follow the Ax story through selected runnable examples. cpp quick-start advanced-start website/content-src/templates/advanced-start.md quick-start Advanced Start

Advanced Start

Advanced Start is built from runnable C++ examples. The story below follows the same source files that appear under Examples, so code changes start in src/examples/cpp/.

C++ Typed Generation

Start with a typed contract: the model receives named inputs and Ax parses named outputs.

Runs a small typed generation program against OpenAI.

C++
#include "axllm/axllm.hpp"
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <sstream>


int main() {
  const char* key = std::getenv("OPENAI_API_KEY");
  if (key == nullptr || std::string(key).empty()) key = std::getenv("OPENAI_APIKEY");
  if (key == nullptr || std::string(key).empty()) {
    std::cerr << "Set OPENAI_API_KEY or OPENAI_APIKEY to run this example.\n";
    return 2;
  }
  const char* model = std::getenv("AX_OPENAI_MODEL");
  axllm::OpenAICompatibleClient client(axllm::object({
      {"api_key", key},
      {"model", model == nullptr || std::string(model).empty() ? "gpt-5.4-mini" : model},
      {"model_config", axllm::object({{"temperature", 0}})},
  }));
  axllm::AxGen program = axllm::ax("question:string -> answer:string");
  axllm::Value output = program.forward(client, axllm::object({{"question", "In one sentence, explain Ax as a language-agnostic LLM programming library."}}));
  std::cout << axllm::stringify(output) << "\n";
}

C++ Grounded Support Agent

Move to an agent when the model needs a runtime loop and a final typed answer.

Answers a support question grounded in a handbook that is kept out of the model prompt via contextFields.

C++
#include "axllm/axllm.hpp"
#include "axllm/runtime/quickjs/quickjs_runtime.hpp"
#include <cstdlib>
#include <iostream>

int main() {
  const char* key = std::getenv("OPENAI_API_KEY");
  if (key == nullptr || std::string(key).empty()) key = std::getenv("OPENAI_APIKEY");
  if (key == nullptr || std::string(key).empty()) {
    std::cerr << "Set OPENAI_API_KEY or OPENAI_APIKEY to run this example.\n";
    return 2;
  }
  const char* model = std::getenv("AX_OPENAI_MODEL");
  axllm::OpenAICompatibleClient client(axllm::object({
      {"api_key", key},
      {"model", model == nullptr || std::string(model).empty() ? "gpt-5.4-mini" : model},
      {"model_config", axllm::object({{"temperature", 0}})},
  }));

  // The handbook can be arbitrarily large. Listing it in `contextFields` keeps it
  // in the agent's runtime so it never inflates the model prompt -- the agent reads
  // it through code, not through tokens. That is the whole point of an Ax agent
  // over a plain gen() call: the source material stays out of the context window.
  std::string handbook =
      "# Acme Cloud -- Support Handbook\n"
      "\n"
      "## Billing\n"
      "- Invoices are issued on the 1st of each month and are due net-15.\n"
      "- Plan downgrades take effect at the END of the current billing cycle, not immediately.\n"
      "- Refunds are issued to the original payment method within 5 business days.\n"
      "\n"
      "## Access\n"
      "- Seats can be added by any workspace Owner under Settings -> Members.\n"
      "- SSO (SAML) is available on Enterprise; SCIM provisioning is Owner-only.\n"
      "\n"
      "## Incidents\n"
      "- Status and uptime are published at status.acme.example.\n"
      "- Sev-1 incidents page the on-call within 5 minutes; updates post every 30 minutes.\n"
      "\n"
      "## Data\n"
      "- Exports are available in CSV and JSON from Settings -> Data.\n"
      "- Deleted workspaces are recoverable for 30 days, then permanently purged.";

  auto assistant = axllm::agent(
      "question:string, handbook:string -> answer:string, citations:string[] \"Handbook sections the answer relies on\"",
      // Keep the handbook in the runtime, out of the prompt.
      axllm::object({
          {"contextFields", axllm::array({"handbook"})},
          {"runtime", axllm::object({{"language", "JavaScript"}})},
      }));

  axllm::runtime::quickjs::QuickJsCodeRuntime runtime;
  axllm::Value result = assistant.forward(
      client,
      axllm::object({
          {"question", "A customer downgraded their plan today. When does it take effect, and can they get a refund for the current cycle?"},
          {"handbook", handbook},
      }),
      axllm::object({{"runtime", axllm::Core::code_runtime_ref(runtime)}, {"max_actor_steps", 12}}));

  std::cout << axllm::stringify(result) << "\n";
}

C++ Sequential Flow

Use a flow when the application should own the order of multi-step work.

Runs a two-step Ax flow against OpenAI.

C++
#include "axllm/axllm.hpp"
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <sstream>


int main() {
  const char* key = std::getenv("OPENAI_API_KEY");
  if (key == nullptr || std::string(key).empty()) key = std::getenv("OPENAI_APIKEY");
  if (key == nullptr || std::string(key).empty()) {
    std::cerr << "Set OPENAI_API_KEY or OPENAI_APIKEY to run this example.\n";
    return 2;
  }
  const char* model = std::getenv("AX_OPENAI_MODEL");
  axllm::OpenAICompatibleClient client(axllm::object({
      {"api_key", key},
      {"model", model == nullptr || std::string(model).empty() ? "gpt-5.4-mini" : model},
      {"model_config", axllm::object({{"temperature", 0}})},
  }));
  axllm::AxGen step = axllm::ax("documentText:string -> summaryText:string");
  axllm::AxFlow program = axllm::flow(axllm::object({{"id", "examples.sequentialFlow"}}))
      .execute("step", step)
      .map("note", [](axllm::Value) { return axllm::object({{"note", "Mapped flow state after the provider-backed step."}}); })
      .returns(axllm::object({{"step", "step"}, {"note", "note"}}));
  axllm::Value output = program.forward(client, axllm::object({{"documentText", "Ax gives developers signatures, provider clients, agents, flows, tracing, and optimization."}}));
  std::cout << axllm::stringify(output) << "\n";
}

C++ Text To Speech

Add audio when the same provider-backed contract should accept or produce speech.

Generates speech audio through OpenAI.

C++
#include "axllm/axllm.hpp"
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <sstream>
#include <variant>


int main() {
  const char* key = std::getenv("OPENAI_API_KEY");
  if (key == nullptr || std::string(key).empty()) key = std::getenv("OPENAI_APIKEY");
  if (key == nullptr || std::string(key).empty()) {
    std::cerr << "Set OPENAI_API_KEY or OPENAI_APIKEY to run this example.\n";
    return 2;
  }
  const char* model = std::getenv("AX_OPENAI_MODEL");
  axllm::OpenAIResponsesClient client(axllm::object({
      {"api_key", key},
      {"model", model == nullptr || std::string(model).empty() ? "gpt-5.4-mini" : model},
      {"model_config", axllm::object({{"temperature", 0}})},
  }));
  axllm::Value speech = client.speak(axllm::object({{"text", "Ax turns LLM prompts into typed programs."}, {"voice", "alloy"}, {"format", "mp3"}}));
  axllm::Value audio_value = axllm::Core::get(speech, "audio");
  double audio_len = audio_value.is_string() ? static_cast<double>(std::get<std::string>(audio_value.data).size()) : 0.0;
  std::cout << axllm::stringify(axllm::object({{"format", axllm::Core::get(speech, "format")}, {"audioBytesBase64", audio_len}})) << "\n";
}

C++ Adaptive Provider Balancing

Start with a typed contract: the model receives named inputs and Ax parses named outputs.

Routes equivalent chat traffic using shared reliability, latency, and cost statistics.

C++
#include "axllm/axllm.hpp"

#include <cstdlib>
#include <iostream>
#include <memory>
#include <vector>

int main() {
  const char* raw_key = std::getenv("OPENAI_API_KEY");
  if (raw_key == nullptr || std::string(raw_key).empty()) raw_key = std::getenv("OPENAI_APIKEY");
  if (raw_key == nullptr || std::string(raw_key).empty()) {
    std::cerr << "Set OPENAI_API_KEY or OPENAI_APIKEY to run this example.\n";
    return 2;
  }
  const std::string model = std::getenv("AX_OPENAI_MODEL") == nullptr ? "gpt-5.4-mini" : std::getenv("AX_OPENAI_MODEL");
  auto primary = std::make_shared<axllm::OpenAICompatibleClient>(axllm::object({{"api_key", raw_key}, {"model", model}}));
  auto backup = std::make_shared<axllm::OpenAICompatibleClient>(axllm::object({{"api_key", raw_key}, {"model", model}}));

  auto store = std::make_shared<axllm::AxInMemoryBalancerStatsStore>();
  std::vector<std::string> route_keys{"openai-primary", "openai-backup"};
  std::vector<std::string> events;
  auto strategy = std::make_shared<axllm::AxBalancerAdaptiveStrategy>();
  strategy->deadline_ms = 6'000;
  strategy->bad_outcome_cost = 0.02;
  strategy->expected_tokens = axllm::object({{"promptTokens", 1'200}, {"completionTokens", 300}});
  strategy->name_space = "support-summary-v1";
  strategy->route_key = [route_keys](const std::shared_ptr<axllm::AxAIService>&, std::size_t index) { return route_keys.at(index); };
  strategy->slice = [](axllm::Value context) { return axllm::Core::truthy(axllm::Core::get(axllm::Core::get(context, "options"), "stream")) ? "streaming" : "interactive"; };
  strategy->stats_store = store;
  strategy->on_routing_event = [&events](axllm::Value event) { events.push_back(axllm::display(axllm::Core::get(event, "type"))); };

  axllm::AxBalancerOptions options;
  options.strategy = strategy;
  axllm::AxBalancer balancer({primary, backup}, options);
  auto response = balancer.chat(axllm::object({{"model", model}, {"chat_prompt", axllm::array({axllm::object({{"role", "user"}, {"content", "Summarize why shared routing state matters."}})})}}));
  std::cout << axllm::stringify(response) << "\n" << events.size() << " routing events\n";
}

C++ AxGen Optimization

Close the loop by measuring examples and applying optimizer artifacts to the program.

Runs a baseline OpenAI prediction and applies an optimizer artifact.

C++
#include "axllm/axllm.hpp"
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <sstream>

struct ExampleOptimizer : axllm::OptimizerEngine {
  std::string name() const override { return "example"; }
  std::string version() const override { return "1"; }
  axllm::Value optimize(axllm::Value request) override { return optimize(std::move(request), nullptr); }
  axllm::Value optimize(axllm::Value, axllm::OptimizerEvaluator*) override {
    return axllm::object({{"componentMap", axllm::object({{"priority::instruction", "Classify operational risk. Use high for production-impacting urgency."}})}, {"metadata", axllm::object({{"source", "axgen"}})}});
  }
};

int main() {
  const char* key = std::getenv("OPENAI_API_KEY");
  if (key == nullptr || std::string(key).empty()) key = std::getenv("OPENAI_APIKEY");
  if (key == nullptr || std::string(key).empty()) {
    std::cerr << "Set OPENAI_API_KEY or OPENAI_APIKEY to run this example.\n";
    return 2;
  }
  const char* model = std::getenv("AX_OPENAI_MODEL");
  axllm::OpenAICompatibleClient client(axllm::object({
      {"api_key", key},
      {"model", model == nullptr || std::string(model).empty() ? "gpt-5.4-mini" : model},
      {"model_config", axllm::object({{"temperature", 0}})},
  }));
  axllm::AxGen program = axllm::ax("emailText:string -> priority:class \"high, normal, low\", rationale:string", axllm::object({{"id", "priority"}, {"instruction", "Classify the email priority."}}));
  axllm::Value baseline = program.forward(client, axllm::object({{"emailText", "Production checkout is failing for enterprise customers."}}));
  ExampleOptimizer optimizer;
  axllm::Value artifact = program.optimize_with(optimizer, axllm::array({axllm::object({{"emailText", "URGENT: checkout is down"}, {"priority", "high"}})}), axllm::object({{"apply", false}}));
  program.apply_optimization(artifact);
  axllm::Value after = program.forward(client, axllm::object({{"emailText", "Production checkout is failing for enterprise customers."}}));
  std::cout << axllm::stringify(axllm::object({{"baseline", baseline}, {"after", after}})) << "\n";
}

C++ Native MCP Tools

Use this runnable example as the next step in the Ax path.

Attaches a live MCP client directly to AxGen without a lossy function adapter.

C++
#include "axllm/mcp.hpp"
#include <cstdlib>
#include <iostream>
#include <memory>

int main() {
  const char* key = std::getenv("OPENAI_API_KEY"); if (!key) key = std::getenv("OPENAI_APIKEY");
  const char* endpoint = std::getenv("MCP_URL");
  if (!key || !endpoint) return 2;
  auto transport = std::make_shared<axllm::AxMCPStreamableHTTPTransport>(endpoint);
  auto mcp = std::make_shared<axllm::AxMCPClient>(transport, axllm::object({{"namespace", "inventory"}}));
  axllm::AxExecutionContext context({mcp});
  auto program = axllm::ax("request:string -> answer:string");
  context.attach(program);
  axllm::OpenAICompatibleClient llm(axllm::object({{"api_key", key}, {"model", "gpt-5.4-mini"}}));
  auto catalog = mcp->inspect_catalog();
  std::cout << "MCP catalog: " << axllm::Core::iter(catalog.tools).size() << " tools, "
            << axllm::Core::iter(catalog.resources).size() << " resources, "
            << axllm::Core::iter(catalog.resource_templates).size() << " templates\n";
  std::cout << axllm::stringify(program.forward(llm, axllm::object({{"request", "Reindex inventory."}}))) << "\n";
  mcp->close();
}

C++ MCP Resource Wake

Use this runnable example as the next step in the Ax path.

Subscribes over real Streamable HTTP and lets AxEventRuntime wake an authenticated Agent automatically.

C++
#include "axllm/mcp.hpp"
#include "axllm/runtime/quickjs/quickjs_runtime.hpp"
#include <chrono>
#include <condition_variable>
#include <cstdlib>
#include <iostream>
#include <mutex>

int main() {
  const char* key=std::getenv("OPENAI_API_KEY");if(!key)key=std::getenv("OPENAI_APIKEY");const char* endpoint=std::getenv("AX_MCP_ENDPOINT");if(!key||!endpoint)return 2;std::string url(endpoint);bool local=url.rfind("http://127.0.0.1",0)==0;
  auto transport=std::make_shared<axllm::AxMCPStreamableHTTPTransport>(url,axllm::object({{"ssrfProtection",axllm::object({{"requireHttps",!local},{"allowLocalhost",local},{"allowPrivateNetworks",local}})}}));auto client=std::make_shared<axllm::AxMCPClient>(transport,axllm::object({{"namespace","inventory"}}));auto source=std::make_shared<axllm::AxMCPEventSource>(client,"inventory","tenant:demo","authenticated",axllm::AxMCPResourceSubscriptionPolicy::all());
  auto program=axllm::agent("uri:string -> summary:string",axllm::object({{"runtime",axllm::object({{"language","JavaScript"}})}}));axllm::OpenAICompatibleClient llm(axllm::object({{"api_key",key},{"model","gpt-5.4-mini"}}));axllm::runtime::quickjs::QuickJsCodeRuntime js;std::mutex mutex;std::condition_variable changed;bool complete=false;
  axllm::AxEventTarget target;target.id="inventory-agent";target.retrySafety="idempotent";target.mapInput=[](const axllm::AxEventEnvelope& event,const axllm::AxEventContinuation*){return axllm::object({{"uri",axllm::Core::get(event.data,"uri")}});};target.invoke=[&](axllm::Value input,const axllm::AxEventInvocationContext&){auto output=program.forward(llm,input,axllm::object({{"runtime",axllm::Core::code_runtime_ref(js)}}));std::cout<<axllm::stringify(output)<<"\n";{std::lock_guard<std::mutex> lock(mutex);complete=true;}changed.notify_all();return output;};
  axllm::AxEventRuntime runtime({axllm::AxEventRoute{"resource-wake","wake",axllm::object({{"types",axllm::array({"mcp.resource.updated"})}}),"inventory-agent",true}});runtime.register_target(std::move(target)).add_source(source).start();std::unique_lock<std::mutex> lock(mutex);if(!changed.wait_for(lock,std::chrono::seconds(60),[&]{return complete;}))throw std::runtime_error("Timed out waiting for an MCP resource notification");lock.unlock();runtime.close();client->close();
}

C++ MCP Task Continuation

Use this runnable example as the next step in the Ax path.

Creates an owned continuation and resumes an AxFlow from real MCP progress and terminal task notifications.

C++
#include "axllm/mcp.hpp"
#include <chrono>
#include <condition_variable>
#include <cstdlib>
#include <iostream>
#include <mutex>

int main(){
  const char* key=std::getenv("OPENAI_API_KEY");if(!key)key=std::getenv("OPENAI_APIKEY");const char* endpoint=std::getenv("AX_MCP_ENDPOINT");if(!key||!endpoint)return 2;std::string url(endpoint);bool local=url.rfind("http://127.0.0.1",0)==0;auto transport=std::make_shared<axllm::AxMCPStreamableHTTPTransport>(url,axllm::object({{"ssrfProtection",axllm::object({{"requireHttps",!local},{"allowLocalhost",local},{"allowPrivateNetworks",local}})}}));auto client=std::make_shared<axllm::AxMCPClient>(transport,axllm::object({{"namespace","inventory"}}));client->add_notification_listener([](axllm::Value message){if(axllm::display(axllm::Core::get(message,"method",""))=="notifications/progress")std::cout<<"MCP task progress\n";});client->init();auto result=client->call_tool("start_reindex",axllm::object({{"scope","all"}}));auto task_id=axllm::display(axllm::Core::get(axllm::Core::get(result,"task",axllm::Value::object()),"taskId",""));
  auto status=axllm::ax("taskId:string -> status:string");auto program=axllm::flow(axllm::object({{"id","reindex-flow"}})).execute("status",status).returns(axllm::object({{"status","status"}}));axllm::OpenAICompatibleClient llm(axllm::object({{"api_key",key},{"model","gpt-5.4-mini"}}));std::mutex mutex;std::condition_variable changed;int calls=0;
  axllm::AxEventTarget target;target.id="reindex-flow";target.retrySafety="idempotent";target.waitFor=axllm::array({axllm::object({{"kind","mcp.task"},{"value","taskKey"},{"metadata",axllm::object({{"taskId",task_id}})}})});target.mapInput=[](const axllm::AxEventEnvelope& event,const axllm::AxEventContinuation* continuation){return axllm::object({{"taskId",continuation?axllm::Core::get(continuation->metadata,"taskId"):axllm::Core::get(event.data,"taskId")}});};target.invoke=[&](axllm::Value input,const axllm::AxEventInvocationContext&){auto output=program.forward(llm,input);std::cout<<axllm::stringify(output)<<"\n";{std::lock_guard<std::mutex> lock(mutex);++calls;}changed.notify_all();return output;};
  axllm::AxEventRuntime runtime({axllm::AxEventRoute{"task-start","wake",axllm::object({{"types",axllm::array({"app.task.started"})}}),"reindex-flow"},axllm::AxEventRoute{"task-progress","observe",axllm::object({{"types",axllm::array({"mcp.progress"})}})},axllm::AxEventRoute{"task-resume","resume",axllm::object({{"types",axllm::array({"mcp.task.status"})}}),"reindex-flow"}});runtime.register_target(std::move(target)).start();axllm::AxEventEnvelope event;event.id="task-start";event.source="app://tasks";event.type="app.task.started";event.data=axllm::object({{"taskId",task_id},{"taskKey","inventory:"+task_id}});runtime.publish(event,"tenant:demo","authenticated");auto source=std::make_shared<axllm::AxMCPEventSource>(client,"inventory","tenant:demo","authenticated");source->start_scoped([&](axllm::AxEventEnvelope inbound,std::string scope,std::string trust){runtime.publish(inbound,scope,trust);});std::cout<<"Waiting for terminal MCP task notification "<<task_id<<"\n";std::unique_lock<std::mutex> lock(mutex);if(!changed.wait_for(lock,std::chrono::seconds(60),[&]{return calls>=2;}))throw std::runtime_error("Timed out waiting for the MCP task continuation");lock.unlock();source->close();runtime.close();client->close();
}
Docs