Skip to main content
Source Code: cpp/ in the GAIA repository.
See also: Overview for architecture, execution flow, and getting started.

Error Handling & Recovery

The framework handles failures at every layer — LLM connection, JSON parsing, and tool execution — so your agent doesn’t crash on transient errors.

LLM Connection Failures

If the LLM server is unreachable or returns an error, the agent retries once automatically, then exits gracefully:
The return value on LLM failure:
Your application should check the result field — there is no exception to catch. HTTP timeouts: 30s connection, 120s read.

Malformed JSON Recovery

Local LLMs often return imperfect JSON. The parser applies six extraction strategies in sequence:
  1. Direct JSON parse
  2. Extract from markdown code blocks (```json ... ```)
  3. Bracket-matching — find first complete {...} in mixed text
  4. Fix common syntax errors (trailing commas, single quotes, missing brackets)
  5. Regex extraction of individual fields ("thought", "tool", "answer")
  6. Treat entire response as a plain-text conversational answer
This means the agent recovers from most LLM formatting errors without any intervention.

Tool Execution Errors

When a tool callback throws an exception or returns {"status": "error", ...}, the agent enters error recovery mode:
  1. The error is captured (exceptions are caught, not propagated)
  2. The error context is sent back to the LLM: “Tool execution failed. Please try an alternative approach.”
  3. The LLM reasons about the error and may try a different tool or strategy
  4. If the LLM cannot recover within maxSteps, the agent returns the last error as the result
Tool errors never crash the agent. The error flow:

MCP Auto-Reconnect

If an MCP server disconnects mid-session (process crash, timeout), the agent reconnects automatically:
The subprocess is re-launched and re-initialized. If reconnection fails, the tool call returns an error and the LLM is notified.

Loop Detection

The agent detects infinite tool call loops — when the LLM calls the same tool with the same arguments 4+ times in a row. When detected, the agent stops and returns:

Thread Safety

Blocking Semantics

processQuery() is fully blocking. It runs the complete agent loop (LLM calls, tool executions, history management) on the calling thread and returns only when a final answer is produced or the step limit is reached. This means:
  • Do not call processQuery() from a UI thread — it will freeze the UI for the duration of the agent run
  • Use a background thread or async wrapper for GUI integration

Concurrent Agent Instances

Different Agent instances are fully independent and can run in parallel on separate threads. Each agent owns its own conversation history, tool registry, MCP connections, and output handler.

Single-Agent Rules

Do NOT call processQuery() concurrently on the same agent instance. The Agent enforces this with an atomic in-flight guard: the second concurrent call throws std::runtime_error("Agent::processQuery is not re-entrant"). Callers must serialize access to a single Agent (or create one per thread/session).
Similarly, do not call connectMcpServer() or disconnectMcpServer() while processQuery() is running.

Security Model

Tool Registration Is Explicit

Only tools registered via registerTool() or discovered from a connected MCP server are available. There is no reflection, auto-discovery, or dynamic code execution. The LLM can only call tools that your code has explicitly registered.

Tool Callback Responsibility

The framework does not validate tool arguments before passing them to your callback. Each tool is responsible for:
  • Validating its input parameters (types, ranges, formats)
  • Sanitizing paths and shell arguments
  • Rejecting unexpected or dangerous inputs
Example — a safe file-reading tool:

MCP Server Trust

MCP servers are trusted implicitly — all tools they expose are registered without review. Only connect to MCP servers you control. In production, audit the tool list returned by each server before deployment.

Prompt Injection

The LLM decides which tool to call based on user input and conversation history. A malicious user could craft input that causes the LLM to misuse a tool. Mitigations:
  • Validate in the tool callback — don’t trust the LLM’s argument choices blindly
  • Use restrictive tool descriptions — describe exactly what the tool does and what arguments it accepts
  • Limit tool scope — register only the tools needed for your use case
  • Consider confirmation flows — for destructive operations, require user confirmation before executing

Conversation History

Conversation history persists between processQuery() calls on the same agent. Previous queries and tool results are visible to subsequent LLM calls. For multi-user scenarios, create a new Agent instance per user session to prevent data leakage.

Production Deployment

Binary Sizes

Measured with MSVC 2022 Release build (x64): The static library is large because it bundles all dependencies. When building as a shared library (DLL), the binary is significantly smaller since dependencies are linked dynamically.

DLL / Shared Library

The framework supports both static and shared library builds. DLL export macros (GAIA_API) are already applied to all public classes:
When consuming the DLL, the GAIA_API macro automatically switches from __declspec(dllexport) to __declspec(dllimport).

Install Targets

The CMake install target produces a complete SDK package:
This creates:
Consumers use find_package(gaia_core) to link against the installed SDK.

Runtime Configuration

The LLM endpoint can be configured at runtime via environment variable — no recompilation needed:
All other AgentConfig fields are set at construction time. For dynamic configuration, read from a config file or registry in your makeConfig() function.

HTTPS Support

HTTPS is enabled automatically when CMake finds OpenSSL on the system (find_package(OpenSSL QUIET) in cpp/CMakeLists.txt). If OpenSSL is not present, GAIA builds with HTTP-only transport and skips the SSL-specific code paths. There is no GAIA_ENABLE_SSL option — to force-disable OpenSSL, pass the CMake built-in -DCMAKE_DISABLE_FIND_PACKAGE_OpenSSL=ON:

API Quick Reference

Agent

ToolRegistry

OutputHandler

Subclass to integrate agent output with your own UI. All methods are virtual:
See the Custom Agent guide for full OutputHandler examples including headless/embedded usage.

MCPClient


Vision Language Models (VLM)

The C++ SDK supports vision-language models (VLMs) via the OpenAI-compatible /chat/completions endpoint. Images are sent inline as base64 data URIs.

gaia::Image

Supported formats: image/png, image/jpeg, image/gif, image/webp, image/bmp. Unsupported MIME types and empty buffers throw std::invalid_argument. Size cap. Image::fromFile rejects files larger than GAIA_MAX_IMAGE_BYTES (default 20 MiB, compile-time override). It also rejects non-regular files (directories, symlinks, FIFOs, devices) for safety.

Sending images with processQuery

Two new overloads accept images:
Context size. VLM models require a large context window — 32768 is the recommended minimum. Smaller values (e.g. 2048) will surface a raw server error as std::runtime_error from processQuery. History semantics. Both overloads are stateful and symmetric with the string overload: they read conversationHistory_ as request context, and append the input user messages (with image parts stripped) plus the assistant’s final answer. Image base64 is never retained in history. Thread safety. Agent is not re-entrant — concurrent processQuery calls on the same Agent throw std::runtime_error. See Thread Safety above.

End-to-end example

See cpp/examples/vlm_agent.cpp:

Next Steps

Overview

Architecture, execution flow, and getting started

Custom Agent

Custom prompts, typed tools, MCP servers, and output capture

Integration Guide

Consume gaia_core in your own CMake project

Quickstart

Prerequisites, build steps, and running your first demo