Skip to content

agent-lead

Tech lead agent — reviews and assigns tasks

agent-lead is the orchestration (tech-lead) agent for the KĀDI multi-agent system. It coordinates task assignment, verification, PR creation, and quest cleanup. It listens to quest and task events on the broker, calls remote quest APIs via KadiClient, maintains presence via heartbeat/registration, and wires repository and provider configuration for worker orchestration and optional LLM-enhanced workflows.

  • Base runtime: extends the project-wide BaseAgent (provides KadiClient, ProviderManager, MemoryService, logger utilities).
  • Event-driven: subscribes to broker topics and dispatches to handlers:
    • Listens: quest.tasks_ready, task.validated, task.failed, task.revision_needed, quest.cascade_needed, quest.verification_complete, quest.merged
    • Publishes: task.assigned, quest.verification_complete, conflict.escalation
  • Key components:
    • Entrypoint: src/index.ts — bootstraps configuration, role/broker resolution, registration and heartbeat, and wires handler modules.
    • Handlers: setupTaskReceptionHandler, setupTaskVerificationHandler, setupPrWorkflowHandler, setupQuestCleanupHandler (imported from ./handlers/*).
    • Provider integration: provider.PRIMARY / provider.FALLBACK configured in config.toml; BaseAgent’s ProviderManager selects model endpoints (e.g., model-manager, anthropic).
    • Memory/Repo: MemoryService stores logs under memory.DATA_PATH; REPO_PATH is exported from config → env for handlers that manipulate the repository (pr-workflow, quest-cleanup).
  • How it fits in AGENTS ecosystem:
    • Acts as the lead/coordinator that registers with the quest server (via KadiClient remote invocations) and delegates tasks to worker agents on networks configured per-role.
    • Uses broker(s) (local and/or remote) to subscribe/publish events to other agents and systems.

The package does not register standalone “tools” in this snippet, but exposes and uses the following exported/internal functions and handler setup functions. These are the primary integration points to modify behavior or wire additional handlers.

NameDescriptionKey parameters / notes
setupTaskReceptionHandlerInstalls the handler for incoming tasks (quest.tasks_ready → assign/dispatch).No args listed here — imported from ./handlers/task-reception.js; will be given the BaseAgent / KadiClient per runtime wiring.
setupTaskVerificationHandlerInstalls verification logic for completed tasks (task.validated, task.failed).Imported from ./handlers/task-verification.js.
setupPrWorkflowHandlerPR creation and merge workflow handler (creates PRs, uses repo path).Imported from ./handlers/pr-workflow.js; expects REPO_PATH env to be set.
setupQuestCleanupHandlerCleans up quest state after merge/verification.Imported from ./handlers/quest-cleanup.js.
registerAgent(kadiClient, agentRole, leadAgentId)Registers lead agent with the quest server via KadiClient.invokeRemote(‘quest_quest_register_agent’, …).agentRole: string, leadAgentId: string. Parses JSON result from remote.
sendHeartbeat(kadiClient, leadAgentId)Invokes ‘quest_quest_agent_heartbeat’ to report availability.leadAgentId: string.
startHeartbeat(kadiClient, leadAgentId)Starts repeating heartbeat (30s interval). Returns NodeJS.Timeout.Uses sendHeartbeat internally.
unregisterAgent(kadiClient, leadAgentId)Gracefully unregisters from quest server via ‘quest_quest_unregister_agent’.leadAgentId: string.

Abilities (declared in agent.json) that this agent relies on:

  • secret-ability — vault secret access for model/provider keys
  • ability-file-local — for local repo manipulation (PRs, patches)
  • ability-log — structured logging

If additional tool registration code is present in handlers, document those tools accordingly (handlers are where agent behavior is implemented).

Primary configuration is read via readConfig() from agents-library and comes from config.toml. Important fields and environment variables:

config.toml fields:

  • [agent]
    • ID — “agent-lead”
    • VERSION — agent version
    • ROLE — default role when AGENT_ROLE env is not set (e.g., “programmer”)
  • [logging]
    • LEVEL — “debug” / “info” / etc.
  • [broker.local] / [broker.remote]
    • URL — websocket broker address (remote default: wss://broker.dadavidtseng.com/kadi)
    • NETWORKS — array of network names (optional per-broker)
  • [provider]
    • PRIMARY — primary model provider key (e.g., “model-manager”)
    • FALLBACK — fallback provider (e.g., “anthropic”)
  • [provider.]
    • MODEL — model id to use for that provider (e.g., “gpt-5-mini”, “claude-haiku-4-5-20251001”)
  • [memory]
    • DATA_PATH — where memory/log data is stored (e.g., ”./data/memory”)
  • [repo]
    • PATH — default repo path used by PR workflows (wired to process.env.REPO_PATH if not already set)
  • [secrets]
    • VAULTS — list of required vault names ([“anthropic”,“model-manager”,“arcadedb”])
    • KEYS — list of secret keys required
  • [arcadedb]
    • HOST, PORT, USERNAME, DATABASE — arcade DB connection details
  • [roles.]
    • NETWORKS — per-role broker networks (e.g., roles.programmer.NETWORKS = [“producer”,“programmer”,“git”,“qa”,“deploy”,“quest”,“file”,“global”])

Environment variables used:

  • AGENT_ROLE — overrides config.toml agent.ROLE; used to pick per-role networks and form agent name (e.g., agent-lead-programmer)
  • REPO_PATH — repository path for PR and cleanup handlers (set by config if missing)
  • KADI_BROKER_URL_LOCAL / KADI_BROKER_URL_REMOTE — broker override URLs
  • KADI_NETWORK_REMOTE — comma-separated networks for the additional remote broker (if using two brokers)
  • NODE_ENV — influences build/start scripts
  • Secrets delivery: when deployed, secrets are loaded into environment (see deploy.*.services.command in agent.json uses kadi secret receive)

Secrets & Vaults (agent.json + config.toml)

  • agent.json deploy blocks require the following vaults and keys:
    • anthropic: ANTHROPIC_API_KEY
    • model-manager: MODEL_MANAGER_API_KEY, MODEL_MANAGER_BASE_URL
    • arcadedb: ARCADE_USERNAME, ARCADE_PASSWORD
  • config.toml [secrets] mirrors those vaults/keys; secrets are expected to be loaded via agents-library loadVaultCredentials or kadi secret tooling.

Below are direct code excerpts from src/index.ts demonstrating configuration resolution, broker selection, role networks, and the registration/heartbeat primitives.

Role resolution, broker selection and env wiring:

// Role resolution: env var (from kadi run start:artist) takes priority over config.toml
const roleName = process.env.AGENT_ROLE ?? cfg.string('agent.ROLE');
const agentName = `agent-lead-${roleName}`;
// Networks from per-role config section
const roleNetworksKey = `roles.${roleName}.NETWORKS`;
if (!cfg.has(roleNetworksKey)) {
throw new Error(`Unknown role '${roleName}': no [roles.${roleName}] section in config.toml`);
}
const roleNetworks = cfg.strings(roleNetworksKey);
// Broker resolution: at least one of local/remote required
const hasLocal = cfg.has('broker.local.URL');
const hasRemote = cfg.has('broker.remote.URL');
if (!hasLocal && !hasRemote) {
throw new Error('At least one broker required: set [broker.local] or [broker.remote] in config.toml');
}
const brokerUrl = hasLocal
? (process.env.KADI_BROKER_URL_LOCAL ?? cfg.string('broker.local.URL'))
: (process.env.KADI_BROKER_URL_REMOTE ?? cfg.string('broker.remote.URL'));
// Always use role-specific networks for the primary broker connection
const networks = roleNetworks;

Registration + heartbeat functions (exact from source):

async function registerAgent(kadiClient: KadiClient, agentRole: string, leadAgentId: string): Promise<void> {
try {
logger.info(agentId, 'Registering with quest server...', timer.elapsed('main'));
const result = await kadiClient.invokeRemote<{
content: Array<{ type: string; text: string }>;
}>('quest_quest_register_agent', {
agentId: leadAgentId,
name: `Lead ${agentRole.charAt(0).toUpperCase() + agentRole.slice(1)} Agent`,
role: agentRole,
capabilities: LEAD_CAPABILITIES,
maxConcurrentTasks: 10,
});
const resultText = result.content[0].text;
const data = JSON.parse(resultText);
logger.info(agentId, `Registered: ${data.message}`, timer.elapsed('main'));
} catch (error: any) {
logger.error(agentId, `Registration failed: ${error.message}`, timer.elapsed('main'), error);
}
}
async function sendHeartbeat(kadiClient: KadiClient, leadAgentId: string): Promise<void> {
try {
await kadiClient.invokeRemote('quest_quest_agent_heartbeat', {
agentId: leadAgentId,
status: 'available',
currentTasks: [],
timestamp: new Date().toISOString(),
});
} catch (error: any) {
logger.warn(agentId, `Heartbeat failed: ${error.message}`, timer.elapsed('main'));
}
}
function startHeartbeat(kadiClient: KadiClient, leadAgentId: string): NodeJS.Timeout {
logger.debug(agentId, 'Starting heartbeat (30s interval)', timer.elapsed('main'));
sendHeartbeat(kadiClient, leadAgentId).catch(() => {});
return setInterval(() => sendHeartbeat(kadiClient, leadAgentId).catch(() => {}), 30_000);
}

Example: wiring handlers (import lines and how they are referenced at top of file):

import {
BaseAgent,
loadVaultCredentials,
readConfig,
setLogLevel,
setAgentTag,
logger,
timer,
loadDirective,
} from 'agents-library';
import type { BaseAgentConfig, AgentRole } from 'agents-library';
import type { KadiClient } from '@kadi.build/core';
import { setupTaskReceptionHandler } from './handlers/task-reception.js';
import { setupTaskVerificationHandler } from './handlers/task-verification.js';
import { setupPrWorkflowHandler } from './handlers/pr-workflow.js';
import { setupQuestCleanupHandler } from './handlers/quest-cleanup.js';

Handlers are the right place to add custom task routing, verification rules, or PR templates.

Runtime dependencies (from package deps):

  • @anthropic-ai/sdk — Anthropics SDK for models (fallback provider)
  • @kadi.build/core — Kadi protocol client types and remote invocation utilities
  • agents-library — BaseAgent, config, logging, provider manager, memory service, loadVaultCredentials
  • zod — runtime schema validation helper used by agents-library or handlers

Dev deps:

  • typescript, tsx, vitest, eslint, etc.

Abilities declared in agent.json:

  • secret-ability
  • ability-file-local
  • ability-log (version ^0.1.5)

What depends on agent-lead (logical consumers):

  • Quest server (remote RPCs invoked by this agent; expects agent registration and heartbeats)
  • Worker agents and networks listed in role NETWORKS — agent-lead assigns tasks which worker agents subscribe to on the broker
  • PR / repo tooling — pr-workflow and quest-cleanup handlers that manipulate repository and create PRs rely on REPO_PATH and ability-file-local
  • Add or modify handler behavior in ./handlers/* — that’s where task routing, verification, PR creation and cleanup logic live.
  • To change provider selection or model IDs, update [provider] sections in config.toml (PRIMARY/FALLBACK and provider..MODEL).
  • Secrets must be provided via configured vaults (see deploy.* in agent.json). Use loadVaultCredentials from agents-library where needed.
  • When running locally for a different role, set AGENT_ROLE and/or use role-specific npm scripts (e.g., npm run dev:artist).