Skip to content

agent-producer

Orchestrator agent — decomposes quests into tasks

agent-producer is the “producer” orchestration agent in the AGENTS ecosystem. It registers a set of KĀDI tools used for orchestration tasks: listing tools, triggering task execution, and handling human approval workflows for quests and tasks. It exists to expose orchestration primitives to human-facing bots (Slack/Discord) and other agents (agent-lead, mcp-server-quest) via the KĀDI broker so quests can be approved, rejected, revised, and have their tasks dispatched to worker agents.

  • Agent role: ROLE = “producer”. It acts as an orchestration/orchestrator agent that:
    • Registers local tools (client.registerTool) for human or agent invocations.
    • Calls remote server tools (client.invokeRemote) to interact with mcp-server-quest (prefixed remote tool names like quest_quest_submit_approval and quest_quest_update_task).
    • Publishes events (e.g., quest.tasks_ready) to agent-lead so worker agents will pick up and execute tasks.
    • Provides utilities (list_tools) so LLMs or chatbots can discover available tools in a human-friendly format.
  • Key components:
    • KadiClient (from @kadi.build/core): used to register tools, read agent metadata (client.readAgentJson()), invoke remote tools, and publish events.
    • Tools (src/tools/*.ts): each tool registers itself using client.registerTool.
    • LlmOrchestrator (services/llm-orchestrator): lazy-injected into approval tools via setQuestApprovalOrchestrator to allow orchestrator-level features.
    • Abilities declared in agent.json (secret-ability, ability-log) supply runtime features like secret retrieval and structured logging.
  • Data flow examples:
    • Approval: A human triggers quest_approve → agent-producer tool calls client.invokeRemote(‘quest_quest_submit_approval’, …) → mcp-server-quest processes decision and responds → agent-producer returns structured ApprovalOutput.
    • Task execution: A user triggers task_execution → agent-producer publishes quest.tasks_ready to agent-lead → agent-lead assigns to worker agents.
  • Integration points:
    • Broker networks: configured broker URLs (see config.toml for local/remote) and network scoping (tools may be registered only on particular networks such as global, slack, discord).
    • Secrets vaults: required API keys and credentials are declared in agent.json deploy and config.toml [secrets].

The agent registers the following tools. Tools are discoverable on the networks defined by TOOL_NETWORK_SCOPE.

Tool nameRegistration functionDescriptionKey input fieldsRegistered networks
echoregisterEchoToolPlaceholder: echoes text and returns length. Useful for testing.text: stringglobal
list_toolsregisterListToolsToolHuman-readable listing of available tools (local + network). Returns structured + presentation output.(none)global
task_executionregisterTaskExecutionToolTriggers execution of assigned tasks by publishing quest.tasks_ready for agent-lead.questId?, taskId?, taskIds?, _context?global
quest_approveregisterQuestApproveToolSubmit quest-level approval (approve). Calls mcp-server-quest via broker.questId, feedback, userId, platformglobal, slack, discord
quest_request_revisionregisterQuestRequestRevisionToolRequest revision for a quest plan. Calls mcp-server-quest.questId, feedback, userId, platformglobal, slack, discord
quest_rejectregisterQuestRejectToolReject a quest plan. Calls mcp-server-quest.questId, feedback, userId, platformglobal, slack, discord
task_approveregisterTaskApproveToolApprove a completed task (status -> completed). Calls mcp-server-quest quest_update_task.questId, taskId, feedbackglobal, slack, discord
task_request_revisionregisterTaskRequestRevisionToolRequest revision for a task (status -> in_progress).questId, taskId, feedbackglobal, slack, discord
task_rejectregisterTaskRejectToolReject a task result (status -> failed).questId, taskId, feedbackglobal, slack, discord

Notes:

  • Approval tool outputs follow approvalOutputSchema (quest-approval.ts) and taskApprovalOutput schema (task-approval.ts).
  • Tools interact with remote services by calling client.invokeRemote with the broker-prefixed tool names (e.g., ‘quest_quest_submit_approval’, ‘quest_quest_update_task’, ‘quest_quest_query_task’).

Non-secret configuration is in config.toml. Secrets are stored in an encrypted vault (secrets.toml) and delivered via the deploy configuration.

Key config.toml fields:

  • [agent]
    • ID = “agent-producer”
    • ROLE = “producer”
    • VERSION = “0.3.4”
  • [logging]
    • LEVEL = “debug”
  • [broker.local] and [broker.remote]
    • URL = “wss://broker.dadavidtseng.com/kadi” (local) / “wss://broker.kadi.build/kadi” (remote)
    • NETWORKS = [“chatbot”, “quest”, “producer”, “file”, “global”, “voice-services”]
  • [bot]
    • TOOL_TIMEOUT_MS = 10000
    • [bot.slack] and [bot.discord] to enable bot integrations and set USER_ID
  • [memory]
    • DATA_PATH = ”./data/memory”
  • [secrets]
    • VAULTS = [“anthropic”, “model-manager”, “arcadedb”]
    • KEYS = [“MODEL_MANAGER_BASE_URL”, “MODEL_MANAGER_API_KEY”, “ANTHROPIC_API_KEY”, “ARCADE_USERNAME”, “ARCADE_PASSWORD”]
  • [arcadedb]
    • HOST, PORT, USERNAME, DATABASE
  • [provider]
    • PRIMARY = “model-manager”
    • FALLBACK = “anthropic”
    • provider.model-manager.MODEL = “gpt-5-mini”
    • provider.anthropic.MODEL = “claude-haiku-4-5-20251001”

Environment variables and secrets:

  • Declared in agent.json deploy -> secrets.vaults required keys:
    • ANTHROPIC_API_KEY
    • MODEL_MANAGER_API_KEY
    • MODEL_MANAGER_BASE_URL
    • ARCADE_USERNAME
    • ARCADE_PASSWORD
  • Deploy also sets env overrides: NODE_ENV, ARCADE_HOST, ARCADE_PORT in the Akash service block.
  • The agent uses a “secret-ability” to fetch secrets at runtime; the deploy step runs “kadi secret receive” before start to provision secrets into environment/vault.

Secrets vault:

  • agent.json deploy: “delivery”: “broker” indicates secrets are delivered via the broker. The agent expects secrets to be present (via kadi secret plugin) at startup.

Key patterns (copied from source):

  • Echo tool registration (src/tools/echo.ts):
export const echoInputSchema = z.object({
text: z.string().describe('Text to echo back')
});
export const echoOutputSchema = z.object({
echo: z.string().describe('Echoed text'),
length: z.number().describe('Length of text')
});
export type EchoInput = z.infer<typeof echoInputSchema>;
export type EchoOutput = z.infer<typeof echoOutputSchema>;
export function registerEchoTool(client: KadiClient): void {
client.registerTool({
name: 'echo',
description: 'Echo back the input text with its length (placeholder tool - replace with your own)',
input: echoInputSchema,
output: echoOutputSchema
}, async (params: EchoInput): Promise<EchoOutput> => {
logger.info(MODULE_AGENT, `Echoing text: "${params.text}"`, timer.elapsed('main'));
const result = {
echo: params.text,
length: params.text.length
};
return result;
});
}
  • Tools registry and network scoping (src/tools/index.ts):
const TOOL_NETWORK_SCOPE: Record<string, string[]> = {
echo: ['global'],
list_tools: ['global'],
task_execution: ['global'],
quest_approve: ['global', 'slack', 'discord'],
quest_request_revision: ['global', 'slack', 'discord'],
quest_reject: ['global', 'slack', 'discord'],
task_approve: ['global', 'slack', 'discord'],
task_request_revision: ['global', 'slack', 'discord'],
task_reject: ['global', 'slack', 'discord'],
};
  • List tools registration (src/tools/list-tools.ts) — demonstrates reading local agent.json and invoking remote broker queries:
export const listToolsInputSchema = z.object({});
export const listToolsOutputSchema = z.object({
status: z.enum(['info', 'complete', 'partial', 'error']).describe('Result status: info for queries, complete for task-completing actions'),
result: z.object({
tools: z.array(z.object({
name: z.string().describe('Tool name'),
description: z.string().describe('Tool description')
})).describe('Array of all available tools'),
count: z.object({
local: z.number().describe('Number of local tools'),
network: z.number().describe('Number of network tools'),
total: z.number().describe('Total number of tools')
}).describe('Tool counts')
}).describe('Structured tool listing data'),
presentation: z.object({
summary: z.string().describe('Short summary for quick reference'),
details: z.string().describe('Full formatted tool list with descriptions'),
format_hint: z.string().describe('Guidance for LLM on how to present this data to the user')
}).describe('Presentation layer for LLM to customize output')
});
export function registerListToolsTool(client: KadiClient): void {
client.registerTool({
name: 'list_tools',
description: 'List all available tools in human-readable format. This is an informational query — it does NOT complete the user\'s task. After listing tools, continue to fulfill the user\'s actual request using the appropriate tool.',
input: listToolsInputSchema,
output: listToolsOutputSchema
}, async (): Promise<ListToolsOutput> => {
logger.info(MODULE_AGENT, 'Listing all available tools', timer.elapsed('main'));
try {
// 1. Get local tools (registered on this agent)
const localTools = client.readAgentJson().tools;
// 2. Get network tools from broker
// ...
}
// ...
});
}
  • Quest approval orchestrator injection and remote call helper (src/tools/quest-approval.ts):
let orchestrator: LlmOrchestrator | null = null;
export function setQuestApprovalOrchestrator(o: LlmOrchestrator): void {
orchestrator = o;
}
async function submitQuestApproval(
client: KadiClient,
questId: string,
decision: 'approved' | 'revision_requested' | 'rejected',
feedback: string,
userId: string,
platform: string,
): Promise<ApprovalOutput> {
try {
const result = await client.invokeRemote<{
content: Array<{ type: string; text: string }>;
}>('quest_quest_submit_approval', {
questId,
decision,
approvedBy: userId,
approvedVia: platform,
feedback: feedback || undefined,
timestamp: new Date().toISOString(),
});
const resultText = result.content[0].text;
const data = JSON.parse(resultText);
return {
success: true,
message: data.message || `Quest ${decision} successfully`,
questId,
decision,
};
} catch (err) {
// ...
}
}
  • Task approval helper that queries task details and updates status (src/tools/task-approval.ts):
async function updateTaskStatus(
client: KadiClient,
questId: string,
taskId: string,
newStatus: 'completed' | 'in_progress' | 'failed',
decision: string,
): Promise<TaskApprovalOutput> {
try {
// First, get the task details to find the assigned agent
const taskResult = await client.invokeRemote<{
content: Array<{ type: string; text: string }>;
}>('quest_quest_query_task', {
taskId,
});
const taskText = taskResult.content[0].text;
const taskData = JSON.parse(taskText);
const assignedAgent = taskData.task?.assignedAgent || taskData.task?.assigned_agent;
if (!assignedAgent) {
return {
success: false,
message: `Cannot ${decision} task: no assigned agent found`,
questId,
taskId,
decision,
newStatus,
};
}
// Update task status using the assigned agent's ID for authorization
// ...
} catch (err) {
// ...
}
}
  • Task execution registration pattern (src/tools/task-execution.ts) — input/output schemas:
export const taskExecutionInputSchema = z.object({
questId: z.string().optional().describe('Quest ID (optional, will use latest quest with assigned tasks if not provided)'),
taskId: z.string().optional().describe('Specific task ID to execute (optional, will execute all assigned tasks if not provided)'),
taskIds: z.array(z.string()).optional().describe('Explicit list of assigned task IDs to execute (preferred over re-querying by status)'),
_context: channelContextSchema.optional().describe('Optional channel context for notifications (automatically injected by Discord/Slack bots)')
});
export const taskExecutionOutputSchema = z.object({
success: z.boolean().describe('Whether task execution was triggered successfully'),
message: z.string().describe('Human-readable result message'),
tasksTriggered: z.number().describe('Number of tasks triggered'),
questId: z.string().optional().describe('Quest ID that was processed'),
taskIds: z.array(z.string()).describe('List of task IDs that were triggered')
});

Declared in agent.json:

  • Abilities:
    • secret-ability: used for secrets management (fetching vault secrets).
    • ability-log: structured logging (version ^0.1.5).
  • Packages used in source:
    • @kadi.build/core (KadiClient types, zod helpers exposed through the KADI package)
    • agents-library (logger, MODULE_AGENT, MODULE_TOOLS, timer utilities)
    • z (zod helper imported from ‘@kadi.build/core’ in this project)
  • External services:
    • mcp-server-quest: invoked remotely for quest/task approval and task queries (via client.invokeRemote to ‘quest_quest_*’ tool names).
    • agent-lead: receives quest.tasks_ready events for task assignment/dispatch.
    • ArcadeDB: used for logging (arcadedb config); credentials come from the secrets vaults.

What depends on agent-producer:

  • Human-facing bots (Slack/Discord) that surface approval actions to users rely on the producer’s approval tools to affect quest state.
  • mcp-client-quest (dashboard) expects approval tools to be available on the global network so the dashboard can invoke them.
  • agent-lead and worker agents depend on producer to publish quest.tasks_ready to start task assignment workflows.
  • To add a new tool: create src/tools/.ts exporting registerYourTool(client: KadiClient) and add it to the tool registry array (src/tools/index.ts). Use the established pattern of input/output schemas and client.registerTool.
  • Network scoping: add an entry to TOOL_NETWORK_SCOPE if the tool should only be discoverable on specific networks (e.g., slack, discord, global).
  • Remote tool naming: when calling server-side tools via the broker, the broker prefixes the tool name with the server name (e.g., quest_submit_approval → quest_quest_submit_approval).
  • Authorization: task approval uses the assigned agent’s ID for authorization. The helper updateTaskStatus queries the task first to find the assigned agent before calling quest_update_task.
  • Secrets: ensure kadi secret receive is run in deploy/startup so secret-ability can provide credentials (agent.json deploy runs a shell command to fetch secrets before starting).

If you need help tracing a specific tool’s lifecycle or adding an LLM-driven orchestrator integration point, point me to the service file (services/llm-orchestrator.ts) and I can document injection points and lifecycle hooks.