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.
Architecture
Section titled “Architecture”- 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].
Tools / API
Section titled “Tools / API”The agent registers the following tools. Tools are discoverable on the networks defined by TOOL_NETWORK_SCOPE.
| Tool name | Registration function | Description | Key input fields | Registered networks |
|---|---|---|---|---|
| echo | registerEchoTool | Placeholder: echoes text and returns length. Useful for testing. | text: string | global |
| list_tools | registerListToolsTool | Human-readable listing of available tools (local + network). Returns structured + presentation output. | (none) | global |
| task_execution | registerTaskExecutionTool | Triggers execution of assigned tasks by publishing quest.tasks_ready for agent-lead. | questId?, taskId?, taskIds?, _context? | global |
| quest_approve | registerQuestApproveTool | Submit quest-level approval (approve). Calls mcp-server-quest via broker. | questId, feedback, userId, platform | global, slack, discord |
| quest_request_revision | registerQuestRequestRevisionTool | Request revision for a quest plan. Calls mcp-server-quest. | questId, feedback, userId, platform | global, slack, discord |
| quest_reject | registerQuestRejectTool | Reject a quest plan. Calls mcp-server-quest. | questId, feedback, userId, platform | global, slack, discord |
| task_approve | registerTaskApproveTool | Approve a completed task (status -> completed). Calls mcp-server-quest quest_update_task. | questId, taskId, feedback | global, slack, discord |
| task_request_revision | registerTaskRequestRevisionTool | Request revision for a task (status -> in_progress). | questId, taskId, feedback | global, slack, discord |
| task_reject | registerTaskRejectTool | Reject a task result (status -> failed). | questId, taskId, feedback | global, 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’).
Configuration
Section titled “Configuration”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.
Code Examples
Section titled “Code Examples”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')});Dependencies
Section titled “Dependencies”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.
Operational notes / developer tips
Section titled “Operational notes / developer tips”- 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.