Skip to content

Commit 0993838

Browse files
kovtcharov-amdOvtcharov
andauthored
ci(cpp): cross-compile matrix + static binary packaging for C++ agents (#1094) (#1406)
## Why this matters Before: the C++ agents in `cpp/` (health, wifi, process, vlm, security-demo) had no distribution path — `build_cpp.yml` only compiled and tested the library, so there was no self-contained binary anyone could download and run, and no `dist/` artifact for the Agent Hub to publish. After: every agent is built as a **static, cross-platform binary** (no runtime DLL/`.so` dependency) and packaged with its `gaia-agent.yaml` + checksum, on Windows, Linux, and macOS. This is the packaging half of Agent Hub Phase 2B (issue #1094). `build_agents.yml` adds the cross-compile matrix — win-x64 (MSVC), linux-x64 (GCC), darwin-arm64 (Clang) — building with `BUILD_SHARED_LIBS=OFF`, vcpkg `*-static` triplets, and the static MSVC runtime. `cpp/packaging/package_agents.py` then emits `dist/<id>/` with the renamed binary, the agent's manifest, and `checksums.sha256`. On a `v*` tag a release job consolidates all platforms into one bundle; actual R2/PyPI publishing is handled by other Agent Hub issues. Windows-only agents (health/wifi/process) build on the win-x64 leg only; security-demo and vlm are portable and prove the Linux/macOS legs. Manifests validate against the `gaia-agent.yaml` parser from #1091. **Isolated from the #1102 Python restructure** — touches only `.github/workflows/`, `cpp/`, and a packaging script. No `src/gaia/` changes. Spec: `docs/spec/agent-hub-restructure.mdx` (Step 9 — CI/CD; `cpp.binaries`/`static_linked`). ## Test plan - [x] `python -c "import yaml; yaml.safe_load(open('.github/workflows/build_agents.yml'))"` — workflow is valid YAML - [x] All five `cpp/agents/*/gaia-agent.yaml` parse via `gaia.hub.manifest.parse` (cpp, static_linked, correct platforms) - [x] `package_agents.py` smoke-tested with synthetic build trees for win-x64 (5 agents) and linux-x64 (2 agents): correct `dist/<id>/` layout, stale Debug copies skipped, checksums written, fails loudly on missing binary - [x] release-bundle merge logic tested locally — preserves all platforms' binaries per agent and regenerates complete checksums (no per-leg collision) - [x] `python util/lint.py --black --isort` passes - [ ] CI matrix green on all three legs (relies on the runners — no local C++ toolchain available to smoke-build) --------- Co-authored-by: Ovtcharov <kovtchar@amd.com>
1 parent dc39106 commit 0993838

11 files changed

Lines changed: 764 additions & 0 deletions

File tree

.github/workflows/build_agents.yml

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2+
# SPDX-License-Identifier: MIT
3+
4+
# Cross-compile matrix + static-binary packaging for the native (C++) Agent Hub
5+
# agents in cpp/. Each matrix leg builds the C++ example agents with static
6+
# linking (BUILD_SHARED_LIBS=OFF + vcpkg *-static triplet + static MSVC runtime)
7+
# so the artifacts have no runtime DLL/.so dependency, then packages each agent
8+
# into dist/<id>/ (binary + gaia-agent.yaml + checksums.sha256) and uploads it.
9+
#
10+
# On a version tag (v*), a release job consolidates every platform's artifacts
11+
# into a single bundle for downstream publishing (R2 / PyPI are handled by other
12+
# Agent Hub issues — this workflow only produces and uploads the artifacts).
13+
#
14+
# Static triplets: Windows uses the built-in x64-windows-static; Linux/macOS use
15+
# the overlay triplets in cpp/triplets/ (no built-in *-static there).
16+
# Manifests + the packaging script: cpp/agents/, cpp/packaging/package_agents.py
17+
# Issue: https://github.com/amd/gaia/issues/1094
18+
19+
name: Agent Binaries (C++)
20+
21+
on:
22+
push:
23+
branches: [ main ]
24+
tags: [ 'v*' ]
25+
paths:
26+
- 'cpp/**'
27+
- '.github/workflows/build_agents.yml'
28+
pull_request:
29+
branches: [ main ]
30+
types: [opened, synchronize, reopened, ready_for_review]
31+
paths:
32+
- 'cpp/**'
33+
- '.github/workflows/build_agents.yml'
34+
merge_group:
35+
workflow_dispatch:
36+
37+
concurrency:
38+
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
39+
cancel-in-progress: true
40+
41+
permissions:
42+
contents: read
43+
44+
jobs:
45+
build:
46+
name: Package (${{ matrix.platform }})
47+
runs-on: ${{ matrix.os }}
48+
if: github.event_name != 'pull_request' || github.event.pull_request.draft == false || contains(github.event.pull_request.labels.*.name, 'ready_for_ci')
49+
50+
strategy:
51+
fail-fast: false
52+
matrix:
53+
include:
54+
- os: windows-latest
55+
platform: win-x64
56+
triplet: x64-windows-static
57+
- os: ubuntu-latest
58+
platform: linux-x64
59+
triplet: x64-linux-static
60+
- os: macos-latest
61+
platform: darwin-arm64
62+
triplet: arm64-osx-static
63+
64+
env:
65+
# vcpkg binary cache so static OpenSSL is built once and reused across runs.
66+
VCPKG_DEFAULT_BINARY_CACHE: ${{ github.workspace }}/.vcpkg-cache
67+
68+
steps:
69+
- uses: actions/checkout@v6
70+
71+
- name: Set up Python
72+
uses: actions/setup-python@v6
73+
with:
74+
python-version: '3.12'
75+
76+
- name: Install packaging deps
77+
run: python -m pip install --upgrade pyyaml
78+
79+
- name: Prepare vcpkg binary cache dir
80+
shell: bash
81+
run: mkdir -p "${VCPKG_DEFAULT_BINARY_CACHE}"
82+
83+
- name: Restore vcpkg binary cache
84+
uses: actions/cache@v5
85+
with:
86+
path: ${{ github.workspace }}/.vcpkg-cache
87+
key: vcpkg-${{ matrix.triplet }}-${{ hashFiles('cpp/vcpkg.json') }}
88+
restore-keys: |
89+
vcpkg-${{ matrix.triplet }}-
90+
91+
- name: Restore FetchContent cache
92+
uses: actions/cache@v5
93+
with:
94+
path: cpp/build/_deps
95+
key: fetchcontent-agents-${{ matrix.platform }}-${{ hashFiles('cpp/CMakeLists.txt') }}
96+
97+
- name: Install static OpenSSL via vcpkg (${{ matrix.triplet }})
98+
shell: bash
99+
run: |
100+
set -euo pipefail
101+
if [ -z "${VCPKG_INSTALLATION_ROOT:-}" ]; then
102+
echo "::error::VCPKG_INSTALLATION_ROOT is not set on this runner"
103+
exit 1
104+
fi
105+
# Linux/macOS have no built-in *-static triplet; cpp/triplets supplies
106+
# them as overlays (Windows still uses its built-in x64-windows-static).
107+
"${VCPKG_INSTALLATION_ROOT}/vcpkg" install "openssl:${{ matrix.triplet }}" \
108+
--overlay-triplets="${GITHUB_WORKSPACE}/cpp/triplets"
109+
110+
- name: Configure CMake (static)
111+
shell: bash
112+
run: |
113+
set -euo pipefail
114+
cmake -B cpp/build -S cpp \
115+
-DCMAKE_BUILD_TYPE=Release \
116+
-DBUILD_SHARED_LIBS=OFF \
117+
-DGAIA_BUILD_TESTS=OFF \
118+
-DGAIA_BUILD_EXAMPLES=ON \
119+
-DGAIA_BUILD_INTEGRATION_TESTS=OFF \
120+
-DGAIA_BUILD_BENCHMARKS=OFF \
121+
-DVCPKG_MANIFEST_MODE=OFF \
122+
-DVCPKG_TARGET_TRIPLET=${{ matrix.triplet }} \
123+
-DVCPKG_OVERLAY_TRIPLETS="${GITHUB_WORKSPACE}/cpp/triplets" \
124+
-DCMAKE_TOOLCHAIN_FILE="${VCPKG_INSTALLATION_ROOT}/scripts/buildsystems/vcpkg.cmake"
125+
126+
- name: Build (Release)
127+
run: cmake --build cpp/build --config Release --parallel
128+
129+
- name: Package agents for ${{ matrix.platform }}
130+
run: >-
131+
python cpp/packaging/package_agents.py
132+
--platform ${{ matrix.platform }}
133+
--build-dir cpp/build
134+
--agents-dir cpp/agents
135+
--out dist
136+
137+
- name: Show packaged artifacts
138+
shell: bash
139+
run: |
140+
echo "=== dist/ (${{ matrix.platform }}) ==="
141+
find dist -type f | sort
142+
echo
143+
echo "=== checksums ==="
144+
find dist -name checksums.sha256 -exec cat {} +
145+
146+
- name: Upload artifacts
147+
uses: actions/upload-artifact@v7
148+
with:
149+
name: cpp-agents-${{ matrix.platform }}
150+
path: dist/
151+
if-no-files-found: error
152+
153+
# On a version tag, consolidate every platform's dist/ into one bundle so a
154+
# downstream job (R2 / PyPI — other issues) has a single artifact to publish.
155+
release-bundle:
156+
name: Consolidate release bundle
157+
runs-on: ubuntu-latest
158+
needs: [build]
159+
if: startsWith(github.ref, 'refs/tags/v')
160+
permissions:
161+
contents: read
162+
steps:
163+
- name: Download all platform artifacts
164+
uses: actions/download-artifact@v8
165+
with:
166+
pattern: cpp-agents-*
167+
path: artifacts
168+
169+
- name: Merge into release bundle
170+
shell: bash
171+
run: |
172+
set -euo pipefail
173+
mkdir -p bundle
174+
# Each artifact is artifacts/cpp-agents-<platform>/<id>/...; merge by id
175+
# so a single bundle/<id>/ holds every platform's binary + the manifest.
176+
# The per-leg checksums.sha256 files collide (same path per id), so drop
177+
# them here and regenerate complete per-agent + aggregate checksums from
178+
# the merged binaries below.
179+
for leg in artifacts/cpp-agents-*; do
180+
[ -d "$leg" ] || continue
181+
cp -r "$leg"/* bundle/
182+
done
183+
find bundle -name checksums.sha256 -delete
184+
185+
: > bundle/CHECKSUMS.sha256
186+
for agent_dir in bundle/*/; do
187+
[ -d "$agent_dir" ] || continue
188+
( cd "$agent_dir"
189+
# Hash every shipped binary (everything except yaml/checksum files).
190+
: > checksums.sha256
191+
for f in *; do
192+
case "$f" in
193+
*.yaml|*.sha256) continue ;;
194+
esac
195+
[ -f "$f" ] || continue
196+
sha256sum "$f" >> checksums.sha256
197+
done
198+
)
199+
agent_id="$(basename "$agent_dir")"
200+
sed "s|\$| (${agent_id})|" "${agent_dir}checksums.sha256" >> bundle/CHECKSUMS.sha256
201+
done
202+
203+
echo "=== release bundle ==="
204+
find bundle -type f | sort
205+
echo "=== aggregated checksums ==="
206+
cat bundle/CHECKSUMS.sha256
207+
208+
- name: Upload release bundle
209+
uses: actions/upload-artifact@v7
210+
with:
211+
name: gaia-cpp-agents-${{ github.ref_name }}
212+
path: bundle/
213+
if-no-files-found: error
214+
215+
agents-build-summary:
216+
name: Agent Binaries Summary
217+
runs-on: ubuntu-latest
218+
needs: [build]
219+
if: always() && !cancelled()
220+
steps:
221+
- name: Check results
222+
run: |
223+
echo "=== Agent Binaries (C++) Summary ==="
224+
echo "Build/package matrix: ${{ needs.build.result }}"
225+
if [[ "${{ needs.build.result }}" == "skipped" ]]; then
226+
echo "Matrix skipped (draft PR — add 'ready_for_ci' label to run)"
227+
exit 0
228+
fi
229+
if [[ "${{ needs.build.result }}" != "success" ]]; then
230+
echo "One or more matrix legs failed"
231+
exit 1
232+
fi
233+
echo "All platform legs packaged successfully"

cpp/CMakeLists.txt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,12 @@
33

44
cmake_minimum_required(VERSION 3.14)
55

6+
# Select the MSVC runtime library via CMAKE_MSVC_RUNTIME_LIBRARY (the static-CRT
7+
# logic below). Must be set before project() to govern the runtime abstraction.
8+
if(POLICY CMP0091)
9+
cmake_policy(SET CMP0091 NEW)
10+
endif()
11+
612
# Auto-detect or bootstrap vcpkg for optional OpenSSL support.
713
# Must be included before project() so the toolchain is set in time.
814
include(${CMAKE_CURRENT_LIST_DIR}/cmake/vcpkg-auto-setup.cmake)
@@ -18,6 +24,18 @@ if(MSVC)
1824
add_compile_options(/EHsc)
1925
endif()
2026

27+
# ---------------------------------------------------------------------------
28+
# Static-binary packaging (Agent Hub, issue #1094)
29+
# ---------------------------------------------------------------------------
30+
# When building against a vcpkg *-static triplet (e.g. x64-windows-static), the
31+
# produced binaries must link the C runtime statically too — otherwise the .exe
32+
# still depends on the MSVC runtime DLLs and is not self-contained. Detect the
33+
# static triplet and switch the MSVC runtime to the static (/MT) variant.
34+
if(MSVC AND DEFINED VCPKG_TARGET_TRIPLET AND VCPKG_TARGET_TRIPLET MATCHES "-static$")
35+
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
36+
message(STATUS "GAIA packaging: static MSVC runtime (/MT) for ${VCPKG_TARGET_TRIPLET}")
37+
endif()
38+
2139
# ---------------------------------------------------------------------------
2240
# Options -- default ON when this is the top-level project, OFF as a sub-
2341
# project so consumers only get the library.

cpp/agents/README.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
<!--
2+
Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
3+
SPDX-License-Identifier: MIT
4+
-->
5+
6+
# C++ agent packaging manifests
7+
8+
Each subdirectory here holds a `gaia-agent.yaml` packaging manifest for a native
9+
(C++) agent. The manifests are the contract between the C++ sources in
10+
[`../examples/`](../examples) and the Agent Hub: they declare identity, target
11+
platforms, and the packaged binary filename per platform.
12+
13+
| id | source | platforms |
14+
|----|--------|-----------|
15+
| `health` | `examples/health_agent.cpp` | win-x64 |
16+
| `wifi` | `examples/wifi_agent.cpp` | win-x64 |
17+
| `process` | `examples/process_agent.cpp` | win-x64 |
18+
| `security-demo` | `examples/security_demo.cpp` | win-x64, linux-x64, darwin-arm64 |
19+
| `vlm` | `examples/vlm_agent.cpp` | win-x64, linux-x64, darwin-arm64 |
20+
21+
`health`, `wifi`, and `process` use Windows-only APIs (`windows.h`, `psapi`,
22+
`netsh`/PowerShell), so they are built only on the `win-x64` matrix leg.
23+
`security-demo` and `vlm` are portable and prove the Linux and macOS legs.
24+
25+
## How packaging works
26+
27+
[`.github/workflows/build_agents.yml`](../../.github/workflows/build_agents.yml)
28+
builds the binaries with **static linking** (`BUILD_SHARED_LIBS=OFF`, vcpkg
29+
`*-static` triplets, static MSVC runtime) so the artifacts have no runtime
30+
DLL/`.so` dependency. Then [`packaging/package_agents.py`](../packaging/package_agents.py)
31+
produces, per agent:
32+
33+
```
34+
dist/<id>/
35+
<id>-<platform>[.exe] # the binary, renamed to match cpp.binaries
36+
gaia-agent.yaml # this manifest
37+
checksums.sha256 # SHA-256 of the binary
38+
```
39+
40+
The packaged binary filename must match the `cpp.binaries.<platform>` value in
41+
the manifest — the packaging script fails loudly otherwise.
42+
43+
## Mapping a manifest to its CMake target
44+
45+
The manifest `id` maps to a CMake executable target via the `TARGET_BY_ID`
46+
table in [`packaging/package_agents.py`](../packaging/package_agents.py). When
47+
you add a new C++ agent, add an entry there and a matching manifest directory.

cpp/agents/health/gaia-agent.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
# Packaging manifest for the native (C++) health agent. The binary is built
5+
# from cpp/examples/health_agent.cpp by .github/workflows/build_agents.yml and
6+
# packaged into dist/health/ by cpp/packaging/package_agents.py.
7+
id: health
8+
name: System Health
9+
version: 0.1.0
10+
description: "Diagnose a slow or unhealthy Windows system via the Windows MCP server."
11+
author: AMD
12+
license: MIT
13+
14+
category: system
15+
tags: [health, system, diagnostics, mcp, windows]
16+
icon: heart-pulse
17+
18+
language: cpp
19+
min_gaia_version: "0.18.0"
20+
models: [Qwen3-4B-Instruct-2507-GGUF]
21+
22+
requirements:
23+
min_memory_gb: 8
24+
platforms: [win-x64]
25+
26+
cpp:
27+
static_linked: true
28+
binaries:
29+
win-x64: health-win-x64.exe
30+
31+
permissions:
32+
- process:read
33+
- system:read
34+
35+
interfaces:
36+
tui: true
37+
cli: false
38+
pipe: false
39+
api_server: false
40+
mcp_server: false

cpp/agents/process/gaia-agent.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# Copyright(C) 2025-2026 Advanced Micro Devices, Inc. All rights reserved.
2+
# SPDX-License-Identifier: MIT
3+
#
4+
# Packaging manifest for the native (C++) process analyst agent. Built from
5+
# cpp/examples/process_agent.cpp by .github/workflows/build_agents.yml.
6+
id: process
7+
name: Process Analyst
8+
version: 0.1.0
9+
description: "PC narrator and process intelligence: detect anomalies, then act on labeled findings."
10+
author: AMD
11+
license: MIT
12+
13+
category: system
14+
tags: [process, system, monitoring, windows]
15+
icon: activity
16+
17+
language: cpp
18+
min_gaia_version: "0.18.0"
19+
models: [Qwen3-4B-Instruct-2507-GGUF]
20+
21+
requirements:
22+
min_memory_gb: 8
23+
platforms: [win-x64]
24+
25+
cpp:
26+
static_linked: true
27+
binaries:
28+
win-x64: process-win-x64.exe
29+
30+
permissions:
31+
- process:read
32+
- process:write
33+
- system:read
34+
35+
interfaces:
36+
tui: true
37+
cli: true
38+
pipe: false
39+
api_server: false
40+
mcp_server: false

0 commit comments

Comments
 (0)