Skip to content

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.

  • 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:
    1. Agent starts, reads config, loads secrets, sets identity/log level, and registers tools.
    2. A client (human/operator or another agent) invokes a tool (e.g., rebuild_game).
    3. 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.
    4. 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.

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 nameDescriptionKey input fieldsKey output fields
shutdown_gameTerminate the running DaemonAgent game processnonesuccess, message, pidsKilled
restart_gameKill → relaunch the game → wait for game.ready (no rebuild)nonesuccess, message, pid, toolCount
rebuild_gameKill → MSBuild → relaunch → wait for game.readyskipBuild?: booleansuccess, message, buildOutput, buildDurationMs, pid, toolCount
package_releasePackage Run/ folder into a release zip (excludes Logs/Screenshots/.pdb)config?: ‘Debug''Release’
create_releaseCreate a GitHub release using the gh CLI and upload the zipzipPath: string, notes?: string, draft?: boolean, prerelease?: booleansuccess, 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 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.

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.

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.