agent-builder
Builder agent — scaffolds new agents and abilities
agent-builder is a KADI agent that provides game lifecycle and release tooling for the DaemonAgent project. It exists to automate common developer tasks: shutting down the running game, rebuilding via MSBuild, relaunching the game, packaging a release zip, and creating a GitHub release using the gh CLI. The agent registers a set of tools with the KADI broker so other agents or CLI users can invoke these operations remotely.
Architecture
Section titled “Architecture”- The agent is a KADI agent that registers tools (KadiClient.registerTool) on startup. Tools are thin adapters that orchestrate the following responsibilities:
- Process management (find, kill, launch) via game-process utilities.
- Build execution via MSBuild (path configured in config.toml).
- Packaging of the Run/ folder into a zip (archiver).
- Creating GitHub releases using gh CLI.
- Waiting for the DaemonAgent game to re-register with KADI (game.ready event).
- Key components:
- tools/ (shutdown-game, restart-game, rebuild-game, package-release, create-release): each exposes a tool registered with the KadiClient.
- tools/game-process.ts: shared helpers for process discovery, killing, launching, MSBuild invocation, and waiting for KADI reconnection.
- tools/index.ts: central registry that calls each tool registration function.
- src/index.ts: agent bootstrap (reads config, loads vault credentials, registers tools).
- Data flow:
- Agent starts, reads config, loads secrets, sets identity/log level, and registers tools.
- A client (human/operator or another agent) invokes a tool (e.g., rebuild_game).
- Tool calls helpers in game-process to find/kill the running game, optionally run MSBuild, launch the game, and wait for the game to emit game.ready.
- Tools return structured results (zod-validated schemas) to the invoker.
How it fits in the AGENTS ecosystem
- The agent depends on the KADI runtime (KadiClient) to register tools and signal results.
- It uses agents-library for logging, config, timer, and vault-related helpers.
- The agent targets the DaemonAgent runtime (a game binary) as the managed process; the game itself re-joins the broker and registers its own tools when ready.
Tools / API
Section titled “Tools / API”All tools are registered via KadiClient.registerTool. The package exports the register functions (used by tools/index.ts) and registerAllTools is the single function used during agent startup to register everything.
Exported registration functions:
- registerShutdownGameTool(client: KadiClient): void
- registerRestartGameTool(client: KadiClient): void
- registerRebuildGameTool(client: KadiClient): void
- registerPackageReleaseTool(client: KadiClient): void
- registerCreateReleaseTool(client: KadiClient): void
- registerAllTools(client: KadiClient): void
Tools summary table
| Tool name | Description | Key input fields | Key output fields |
|---|---|---|---|
| shutdown_game | Terminate the running DaemonAgent game process | none | success, message, pidsKilled |
| restart_game | Kill → relaunch the game → wait for game.ready (no rebuild) | none | success, message, pid, toolCount |
| rebuild_game | Kill → MSBuild → relaunch → wait for game.ready | skipBuild?: boolean | success, message, buildOutput, buildDurationMs, pid, toolCount |
| package_release | Package Run/ folder into a release zip (excludes Logs/Screenshots/.pdb) | config?: ‘Debug' | 'Release’ |
| create_release | Create a GitHub release using the gh CLI and upload the zip | zipPath: string, notes?: string, draft?: boolean, prerelease?: boolean | success, message, url, tag, version |
If you need to inspect the tool registry, tools/index.ts registers every tool via registerAllTools:
/** * Tool Registry for agent-builder * * Registers all game lifecycle management tools with the KĀDI client. */
import type { KadiClient } from '@kadi.build/core';import { logger, MODULE_TOOLS, timer } from 'agents-library';import { registerShutdownGameTool } from './shutdown-game.js';import { registerRestartGameTool } from './restart-game.js';import { registerRebuildGameTool } from './rebuild-game.js';import { registerPackageReleaseTool } from './package-release.js';import { registerCreateReleaseTool } from './create-release.js';
const toolRegistry: Array<(client: KadiClient) => void> = [ registerShutdownGameTool, registerRestartGameTool, registerRebuildGameTool, registerPackageReleaseTool, registerCreateReleaseTool,];
export function registerAllTools(client: KadiClient): void { timer.start('tools-registry'); logger.info(MODULE_TOOLS, `Registering ${toolRegistry.length} tool(s)...`, timer.elapsed('tools-registry'));
for (const register of toolRegistry) { register(client); }
logger.info(MODULE_TOOLS, `Registered ${toolRegistry.length} tool(s)`, timer.elapsed('tools-registry'));}Tool-specific notes:
- create_release uses the gh CLI and requires that gh is installed and available in PATH.
- package_release uses archiver to build the zip; it reads the VERSION file at the repository root to name artifacts.
- rebuild_game invokes runMSBuild() (in game-process.ts) and returns truncated build output on failure.
Configuration
Section titled “Configuration”Configuration is loaded via agents-library.readConfig() and is driven by config.toml. The key config fields found in config.toml are:
agent section
- agent.ID: string — e.g., “agent-builder”
- agent.ROLE: string — e.g., “builder”
- agent.VERSION: string — e.g., “0.1.0”
logging
- logging.LEVEL: string — “debug”, “info”, etc.
broker
- broker.remote.URL: string — WebSocket URL of the KADI broker
- broker.remote.NETWORKS: array of network names (e.g., [“global”])
- build.MSBUILD_PATH: string — absolute path to MSBuild executable
- build.SOLUTION_PATH: string — absolute path to the .sln file
- build.CONFIGURATION: string — “Debug” or “Release”
- build.PLATFORM: string — e.g., “x64”
game
- game.EXE_PATH: string — absolute path to the game executable
- game.WORKING_DIR: string — working directory where Run/ folder lives
- game.READY_TIMEOUT_MS: number — milliseconds to wait for game.ready
- game.KILL_TIMEOUT_MS: number — timeout used when terminating processes
secrets
- secrets.VAULTS: array — vault names to load (example: [“arcadedb”])
- secrets.KEYS: array — names of secret keys to fetch from vaults (example: [“ARCADE_USERNAME”,“ARCADE_PASSWORD”])
arcadedb (example vault target)
- arcadedb.HOST
- arcadedb.PORT
- arcadedb.USERNAME
- arcadedb.DATABASE
Secrets / Vault handling
- index.ts imports loadVaultCredentials from agents-library (bootstrap step), and config.toml’s [secrets] section indicates which vaults and keys to load. Use your organization’s vault provider configured by agents-library; the agent expects loadVaultCredentials to populate environment or an in-memory store consumed by code that needs secrets.
Runtime configuration read helpers
- tools/game-process.ts uses readConfig() to expose a getConfig() helper that maps config keys to runtime values:
const cfg = readConfig();
export function getConfig() { return { gameExePath: cfg.string('game.EXE_PATH'), gameWorkingDir: cfg.string('game.WORKING_DIR'), msbuildPath: cfg.string('build.MSBUILD_PATH'), solutionPath: cfg.string('build.SOLUTION_PATH'), buildConfiguration: cfg.string('build.CONFIGURATION'), buildPlatform: cfg.string('build.PLATFORM'), gameReadyTimeoutMs: cfg.number('game.READY_TIMEOUT_MS'), processKillTimeoutMs: cfg.number('game.KILL_TIMEOUT_MS'), };}Important note: paths in config.toml are Windows-centric (e.g., “C:/Program Files/…”). Adjust to your environment if running elsewhere.
Code Examples
Section titled “Code Examples”Below are representative code excerpts taken directly from the source to show key patterns.
- Finding running game processes (uses tasklist on Windows):
/** * Find running game process PIDs by executable name. * Uses tasklist on Windows. */export function findGameProcesses(): number[] { const config = getConfig(); const exeName = config.gameExePath.split('/').pop() || '';
try { const output = execSync(`tasklist /FI "IMAGENAME eq ${exeName}" /FO CSV /NH`, { encoding: 'utf-8', timeout: 5000, });
const pids: number[] = []; for (const line of output.trim().split('\n')) { // CSV format: "name.exe","PID","Session Name","Session#","Mem Usage" const match = line.match(/"[^"]+","(\d+)"/); if (match) { pids.push(parseInt(match[1], 10)); } } return pids; } catch { return []; }}- Example of a tool registration (rebuild_game): shows the control flow for kill → build → launch → wait for reconnect.
export function registerRebuildGameTool(client: KadiClient): void { client.registerTool({ name: 'rebuild_game', description: 'Full rebuild cycle: shut down game → MSBuild compile → relaunch → wait for KĀDI reconnect', input: inputSchema, output: outputSchema, }, async (params: Input): Promise<Output> => { logger.info(MODULE_AGENT, 'rebuild_game: Starting full rebuild cycle...', timer.elapsed('main'));
// Step 1: Kill existing process const pidsBefore = findGameProcesses(); if (pidsBefore.length > 0) { logger.info(MODULE_AGENT, 'rebuild_game: Killing existing game process...', timer.elapsed('main')); const killed = await killGameProcess(); if (!killed) { return { success: false, message: 'Failed to kill existing game process', buildOutput: '', buildDurationMs: 0, pid: 0, toolCount: 0, }; } }
// Step 2: MSBuild (unless skipped) let buildOutput = ''; let buildDurationMs = 0;
if (!params.skipBuild) { logger.info(MODULE_AGENT, 'rebuild_game: Running MSBuild...', timer.elapsed('main')); const buildResult = runMSBuild(); buildOutput = buildResult.output; buildDurationMs = buildResult.durationMs;
if (!buildResult.success) { // Truncate build output for the response const truncated = buildOutput.length > 2000 ? '...' + buildOutput.slice(-2000) : buildOutput;
return { success: false, message: `MSBuild failed after ${buildDurationMs}ms`, buildOutput: truncated, buildDurationMs, pid: 0, toolCount: 0, }; } }
// Step 3: Launch and wait for reconnect continues... });}- Packaging release example (reads VERSION, creates zip):
const inputSchema = z.object({ config: z.enum(['Debug', 'Release']).optional().describe('Build configuration to package (default: from config.toml)'),});
const outputSchema = z.object({ success: z.boolean().describe('Whether the package was created successfully'), message: z.string().describe('Status message'), zipPath: z.string().describe('Absolute path to the created zip file'), sizeMB: z.number().describe('Zip file size in MB'), version: z.string().describe('Version string from VERSION file'), fileCount: z.number().describe('Number of files included in the zip'),});- Creating a GitHub release (reads VERSION and requires gh CLI):
const inputSchema = z.object({ zipPath: z.string().describe('Absolute path to the release zip file (from package_release)'), notes: z.string().optional().describe('Release notes (markdown). If omitted, auto-generates from version.'), draft: z.boolean().optional().describe('Create as draft release (default: false)'), prerelease: z.boolean().optional().describe('Mark as pre-release (default: false)'),});These snippets demonstrate patterns you will commonly modify: zod schemas for tool validation, use of agents-library logging/timer modules, and how tools orchestrate external processes.
Dependencies
Section titled “Dependencies”Runtime / package dependencies (from source import usage and agent.json):
- agents-library — logging, readConfig, timer, loadVaultCredentials, setLogLevel, setAgentTag, loadDirective
- @kadi.build/core — KadiClient types and zod wrappers used for schemas
- archiver — used by package_release to build zip files
- Node built-ins: child_process (execSync, spawn), fs, path
- External runtime binaries:
- MSBuild.exe — path configured in build.MSBUILD_PATH
- gh CLI — required by create_release
- agent.json declares ability “ability-log” — the agent expects logging ability to be available in the runtime environment.
What depends on agent-builder
- Consumers (human operators, CI systems) that invoke its tools via KADI to perform rebuild/restart/shutdown/package/release workflows.
- The DaemonAgent game itself is the runtime target that this agent manages (the game is expected to reconnect to KADI and register tools; rebuild/restart flows wait for that reconnect and report the tool count).
Notes and modification guidance
- To change how the agent detects game readiness, inspect and modify waitForGameReady() in tools/game-process.ts (it is used by restart_game and rebuild_game).
- To add or change a tool: add a new registerXxxTool in tools/, export it, and add it to the toolRegistry array in tools/index.ts.
- If you change config key names, update getConfig() in tools/game-process.ts and ensure config.toml is updated.
- Use the z.object schemas in each tool to declare inputs/outputs; KADI tooling relies on those for validation and remote callers.
If you need further walkthroughs (e.g., adding a new tool that runs custom build steps, or adjusting process-kill behavior for non-Windows platforms), specify the target change and I can provide a focused code edit example.