Multi-Agent Collaboration

This worked example demonstrates how to orchestrate multiple specialized AI agents in a collaborative discussion using Maitento’s RoundRobin interaction pattern. You’ll learn how to create agents with distinct roles, configure voting and consensus mechanisms, and participate as a human-in-the-loop.


Overview

In this tutorial, we’ll build a Research Review Panel - a multi-agent system where three specialized agents collaborate to evaluate research proposals:

AgentRoleExpertise
ResearcherProposes ideas and provides domain expertiseInnovation, methodology, feasibility
CriticIdentifies weaknesses and challenges assumptionsRisk assessment, logical analysis
SummarizerSynthesizes discussion and proposes conclusionsConsensus building, documentation

The agents will discuss a research proposal in a round-robin fashion, with optional human participation for final approval via voting.


Prerequisites

Before starting, ensure you have:

  • A Maitento account with API access
  • The Maitento SDK installed (dotnet add package Maitento.Sdk)
  • Access to at least one AI model (Claude or GPT recommended)

Project Structure

research-review-panel/
β”œβ”€β”€ research-review-panel.mtr        # MTR project file
β”œβ”€β”€ agents/
β”‚   β”œβ”€β”€ researcher.agent.json        # Researcher agent definition
β”‚   β”œβ”€β”€ critic.agent.json            # Critic agent definition
β”‚   └── summarizer.agent.json        # Summarizer agent definition
β”œβ”€β”€ interactions/
β”‚   └── research-review.roundrobin.json  # RoundRobin interaction
└── Program.cs                       # SDK client code

Step 1: Create Specialized Agents

Researcher Agent

The Researcher agent provides domain expertise and innovative thinking.

agents/researcher.agent.json:

{
  "name": "researcher",
  "aiModel": "claude-sonnet-4-5",
  "systemPrompt": "You are a Research Scientist with expertise in evaluating research proposals. Your role is to:\n\n1. Assess the scientific merit and innovation of proposals\n2. Evaluate methodology and experimental design\n3. Consider feasibility and resource requirements\n4. Suggest improvements and alternative approaches\n\nBe thorough but constructive. Support your assessments with specific reasoning. When you believe a proposal has merit, advocate for it while acknowledging valid concerns from others.",
  "summaryForOtherAgents": "Research Scientist - Evaluates scientific merit, methodology, and feasibility of proposals. Provides domain expertise and constructive suggestions."
}

Critic Agent

The Critic agent identifies potential issues and challenges assumptions.

agents/critic.agent.json:

{
  "name": "critic",
  "aiModel": "claude-sonnet-4-5",
  "systemPrompt": "You are a Critical Reviewer specializing in identifying weaknesses in research proposals. Your role is to:\n\n1. Challenge assumptions and identify logical gaps\n2. Assess potential risks and failure modes\n3. Evaluate statistical validity and sample sizes\n4. Question methodology and reproducibility\n5. Identify ethical concerns or conflicts of interest\n\nBe rigorous but fair. Your goal is to strengthen proposals by exposing weaknesses, not to reject them outright. Provide specific, actionable feedback.",
  "summaryForOtherAgents": "Critical Reviewer - Identifies weaknesses, challenges assumptions, and assesses risks. Provides rigorous analysis to strengthen proposals."
}

Summarizer Agent

The Summarizer agent synthesizes the discussion and builds consensus.

agents/summarizer.agent.json:

{
  "name": "summarizer",
  "aiModel": "claude-sonnet-4-5",
  "systemPrompt": "You are a Discussion Facilitator responsible for synthesizing multi-agent discussions into actionable conclusions. Your role is to:\n\n1. Track key points from all participants\n2. Identify areas of agreement and disagreement\n3. Synthesize diverse viewpoints into balanced summaries\n4. Propose solutions that address raised concerns\n5. When consensus emerges, formulate clear recommendations\n\nRemain neutral and ensure all perspectives are fairly represented. When you believe the discussion has reached a natural conclusion, propose a final recommendation for voting.",
  "summaryForOtherAgents": "Discussion Facilitator - Synthesizes viewpoints, tracks consensus, and proposes final recommendations. Ensures balanced representation of all perspectives."
}

Step 2: Configure the RoundRobin Interaction

The RoundRobin interaction orchestrates the multi-agent discussion with turn-taking, direct questioning, and voting.

interactions/research-review.roundrobin.json:

{
  "name": "research-review",
  "type": "roundrobin",
  "prompt": "Please review the following research proposal and reach a consensus recommendation:\n\n## Research Proposal\n\n**Title:** {{title}}\n\n**Abstract:**\n{{abstract}}\n\n**Methodology:**\n{{methodology}}\n\n**Budget:** {{budget}}\n\n**Timeline:** {{timeline}}\n\n---\n\nDiscuss this proposal collaboratively. Consider scientific merit, feasibility, methodology, and potential impact. When you reach consensus, propose a recommendation (Approve, Reject, or Revise) with specific justification.",
  "solutionSchema": {
    "type": "object",
    "properties": {
      "recommendation": {
        "type": "string",
        "enum": ["Approve", "Reject", "Revise"]
      },
      "confidence": {
        "type": "string",
        "enum": ["High", "Medium", "Low"]
      },
      "summary": {
        "type": "string",
        "description": "Brief summary of the consensus decision"
      },
      "strengths": {
        "type": "array",
        "items": { "type": "string" },
        "description": "Key strengths identified"
      },
      "weaknesses": {
        "type": "array",
        "items": { "type": "string" },
        "description": "Key weaknesses identified"
      },
      "conditions": {
        "type": "array",
        "items": { "type": "string" },
        "description": "Conditions for approval (if applicable)"
      }
    },
    "required": ["recommendation", "confidence", "summary"]
  },
  "agents": [
    { "name": "Researcher", "agent": "researcher" },
    { "name": "Critic", "agent": "critic" },
    { "name": "Summarizer", "agent": "summarizer" }
  ],
  "settings": {
    "isNoActionAllowed": false,
    "isVotingAllowed": true,
    "isDirectQuestioningAllowed": true,
    "isSolutionProposalAllowed": true,
    "maximumNoActionLoops": 3
  },
  "humanSettings": {
    "chatMode": "GoesLast",
    "includeInVotes": true,
    "includeInSolutions": true
  },
  "timeouts": {
    "warningToAi": "PT5M",
    "autoTermination": "PT30M"
  },
  "inputs": [
    { "name": "title", "type": "string", "required": true, "description": "Research proposal title" },
    { "name": "abstract", "type": "string", "required": true, "description": "Research abstract" },
    { "name": "methodology", "type": "string", "required": true, "description": "Proposed methodology" },
    { "name": "budget", "type": "string", "required": false, "description": "Budget estimate" },
    { "name": "timeline", "type": "string", "required": false, "description": "Project timeline" }
  ]
}

Configuration Breakdown

SettingValuePurpose
isVotingAllowedtrueAgents can propose votes on solutions
isDirectQuestioningAllowedtrueAgents can ask specific questions to other agents
isSolutionProposalAllowedtrueAgents can propose final solutions for voting
humanSettings.chatModeGoesLastHuman speaks after all agents in each round
humanSettings.includeInVotestrueHuman vote counts toward consensus
maximumNoActionLoops3Prevents infinite loops if agents have nothing to add

Step 3: Create the MTR Project File

research-review-panel.mtr:

name: research-review-panel
version: 1.0.0
namespace: research

agents:
  - path: agents/researcher.agent.json
  - path: agents/critic.agent.json
  - path: agents/summarizer.agent.json

interactions:
  - path: interactions/research-review.roundrobin.json

Step 4: SDK Implementation

Complete Program.cs

using Maitento.Sdk;
using Maitento.Entities.Agents;
using Maitento.Entities.Interactions;
using System.Text.Json;

namespace ResearchReviewPanel;

public class Program
{
    private const string ApiKey = "your-api-key";
    private const string Namespace = "research";

    public static async Task Main(string[] args)
    {
        await using IMaitentoClient client = MaitentoClient.Create(ApiKey);

        // Step 1: Create the specialized agents
        Console.WriteLine("Creating agents...");
        var agents = await CreateAgents(client);

        // Step 2: Create the RoundRobin interaction
        Console.WriteLine("Creating RoundRobin interaction...");
        var interaction = await CreateInteraction(client, agents);

        // Step 3: Start the research review process
        Console.WriteLine("Starting research review...");
        var process = await StartReview(client, interaction.Id);

        // Step 4: Monitor and participate in the discussion
        Console.WriteLine("Monitoring discussion...");
        await MonitorAndParticipate(client, process.Id);
    }

    private static async Task<Dictionary<string, Agent>> CreateAgents(IMaitentoClient client)
    {
        // Get available AI models
        var models = await client.AiModels.GetAllAsync();
        var claudeSonnet = models.FirstOrDefault(m => m.Name.Contains("claude-sonnet-4-5"))
            ?? throw new Exception("Claude Sonnet model not available");

        var agents = new Dictionary<string, Agent>();

        // Create Researcher agent
        agents["researcher"] = await client.Agents.CreateAsync(
            namespaceName: Namespace,
            name: "researcher",
            aiModelId: claudeSonnet.Id,
            systemPrompt: @"You are a Research Scientist with expertise in evaluating research proposals. Your role is to:

1. Assess the scientific merit and innovation of proposals
2. Evaluate methodology and experimental design
3. Consider feasibility and resource requirements
4. Suggest improvements and alternative approaches

Be thorough but constructive. Support your assessments with specific reasoning. When you believe a proposal has merit, advocate for it while acknowledging valid concerns from others.",
            summaryForOtherAgents: "Research Scientist - Evaluates scientific merit, methodology, and feasibility of proposals."
        );
        Console.WriteLine($"  Created: researcher (ID: {agents["researcher"].Id})");

        // Create Critic agent
        agents["critic"] = await client.Agents.CreateAsync(
            namespaceName: Namespace,
            name: "critic",
            aiModelId: claudeSonnet.Id,
            systemPrompt: @"You are a Critical Reviewer specializing in identifying weaknesses in research proposals. Your role is to:

1. Challenge assumptions and identify logical gaps
2. Assess potential risks and failure modes
3. Evaluate statistical validity and sample sizes
4. Question methodology and reproducibility
5. Identify ethical concerns or conflicts of interest

Be rigorous but fair. Your goal is to strengthen proposals by exposing weaknesses, not to reject them outright.",
            summaryForOtherAgents: "Critical Reviewer - Identifies weaknesses, challenges assumptions, and assesses risks."
        );
        Console.WriteLine($"  Created: critic (ID: {agents["critic"].Id})");

        // Create Summarizer agent
        agents["summarizer"] = await client.Agents.CreateAsync(
            namespaceName: Namespace,
            name: "summarizer",
            aiModelId: claudeSonnet.Id,
            systemPrompt: @"You are a Discussion Facilitator responsible for synthesizing multi-agent discussions. Your role is to:

1. Track key points from all participants
2. Identify areas of agreement and disagreement
3. Synthesize diverse viewpoints into balanced summaries
4. Propose solutions that address raised concerns
5. When consensus emerges, formulate clear recommendations

Remain neutral and ensure all perspectives are fairly represented. When you believe the discussion has reached a natural conclusion, propose a final recommendation for voting.",
            summaryForOtherAgents: "Discussion Facilitator - Synthesizes viewpoints and proposes final recommendations."
        );
        Console.WriteLine($"  Created: summarizer (ID: {agents["summarizer"].Id})");

        return agents;
    }

    private static async Task<RoundRobinInteraction> CreateInteraction(
        IMaitentoClient client,
        Dictionary<string, Agent> agents)
    {
        // Define agent bindings for the interaction
        var agentBindings = new[]
        {
            new InteractionAgentBinding(
                agent: new VersionReference(agents["researcher"].Id, version: 1),
                name: "Researcher",
                externalOperationsMcp: Array.Empty<McpExternalOperationBinding>(),
                externalOperationsOpenApi: Array.Empty<OpenApiExternalOperationBinding>()
            ),
            new InteractionAgentBinding(
                agent: new VersionReference(agents["critic"].Id, version: 1),
                name: "Critic",
                externalOperationsMcp: Array.Empty<McpExternalOperationBinding>(),
                externalOperationsOpenApi: Array.Empty<OpenApiExternalOperationBinding>()
            ),
            new InteractionAgentBinding(
                agent: new VersionReference(agents["summarizer"].Id, version: 1),
                name: "Summarizer",
                externalOperationsMcp: Array.Empty<McpExternalOperationBinding>(),
                externalOperationsOpenApi: Array.Empty<OpenApiExternalOperationBinding>()
            )
        };

        // Define prompt inputs
        var promptInputs = new[]
        {
            new PromptInput("title", "Research proposal title", InteractionInputType.String, isRequired: true),
            new PromptInput("abstract", "Research abstract", InteractionInputType.String, isRequired: true),
            new PromptInput("methodology", "Proposed methodology", InteractionInputType.String, isRequired: true),
            new PromptInput("budget", "Budget estimate", InteractionInputType.String, isRequired: false),
            new PromptInput("timeline", "Project timeline", InteractionInputType.String, isRequired: false)
        };

        // Configure human-in-the-loop settings
        var humanSettings = new RoundRobinInteractionHumanSettings(
            chatMode: HumanChatMode.GoesLast,
            includeInVotes: true,
            includeInSolutions: true
        );

        // Define the solution schema
        var solutionSchema = @"{
            ""type"": ""object"",
            ""properties"": {
                ""recommendation"": { ""type"": ""string"", ""enum"": [""Approve"", ""Reject"", ""Revise""] },
                ""confidence"": { ""type"": ""string"", ""enum"": [""High"", ""Medium"", ""Low""] },
                ""summary"": { ""type"": ""string"" },
                ""strengths"": { ""type"": ""array"", ""items"": { ""type"": ""string"" } },
                ""weaknesses"": { ""type"": ""array"", ""items"": { ""type"": ""string"" } },
                ""conditions"": { ""type"": ""array"", ""items"": { ""type"": ""string"" } }
            },
            ""required"": [""recommendation"", ""confidence"", ""summary""]
        }";

        // Create the RoundRobin interaction
        var interaction = await client.Interactions.CreateRoundRobinAsync(
            namespaceName: Namespace,
            name: "research-review",
            prompt: @"Please review the following research proposal and reach a consensus recommendation:

## Research Proposal

**Title:** {{title}}

**Abstract:**
{{abstract}}

**Methodology:**
{{methodology}}

**Budget:** {{budget}}

**Timeline:** {{timeline}}

---

Discuss this proposal collaboratively. Consider scientific merit, feasibility, methodology, and potential impact. When you reach consensus, propose a recommendation (Approve, Reject, or Revise) with specific justification.",
            solutionSchema: solutionSchema,
            systemPromptMode: InteractionSystemPromptMode.AppendToDefault,
            systemPrompts: new[] { "Engage constructively with other reviewers. Build on their points and address their concerns." },
            durationWarningToAi: TimeSpan.FromMinutes(5),
            durationAutoTermination: TimeSpan.FromMinutes(30),
            externalOperationsMcp: Array.Empty<McpExternalOperationBinding>(),
            externalOperationsOpenApi: Array.Empty<OpenApiExternalOperationBinding>(),
            promptInputs: promptInputs,
            connectorBindingInputs: Array.Empty<ConnectorBindingInput>(),
            connectorAuthenticationSecretInputs: Array.Empty<ConnectorAuthenticationSecretInput>(),
            agents: agentBindings,
            isNoActionAllowed: false,
            isVotingAllowed: true,
            isDirectQuestioningAllowed: true,
            isSolutionProposalAllowed: true,
            humanSettings: humanSettings,
            maximumNoActionLoops: 3
        );

        Console.WriteLine($"  Created interaction: {interaction.Name} (ID: {interaction.Id})");
        return interaction;
    }

    private static async Task<InteractionProcess> StartReview(IMaitentoClient client, Guid interactionId)
    {
        // Sample research proposal
        var inputs = new[]
        {
            new NameValuePair("title", "Machine Learning for Early Cancer Detection in Medical Imaging"),
            new NameValuePair("abstract", @"This proposal outlines a novel approach to early cancer detection using deep learning models trained on multi-modal medical imaging data. By combining CT scans, MRI images, and pathology slides, we aim to develop a unified model that can identify early-stage cancers with 95% sensitivity while maintaining low false positive rates. The model will be validated on a diverse dataset of 50,000 patients across three major medical centers."),
            new NameValuePair("methodology", @"Phase 1: Data collection and preprocessing from partner hospitals
Phase 2: Development of multi-modal fusion architecture
Phase 3: Training on 80% of data with 20% held out for validation
Phase 4: External validation at independent medical centers
Phase 5: Clinical trial integration for real-world testing"),
            new NameValuePair("budget", "$2.5 million over 3 years"),
            new NameValuePair("timeline", "36 months total: 6 months data prep, 12 months development, 12 months validation, 6 months clinical integration")
        };

        var process = await client.InteractionProcesses.StartAsync(
            interactionId: interactionId,
            inputs: inputs
        );

        Console.WriteLine($"  Started process: {process.Id}");
        Console.WriteLine($"  Status: {process.Status}");
        return process;
    }

    private static async Task MonitorAndParticipate(IMaitentoClient client, Guid processId)
    {
        var humanId = Guid.NewGuid(); // In production, use actual user ID
        var humanName = "Dr. Jane Smith";

        while (true)
        {
            // Fetch current process state
            var process = await client.InteractionProcesses.GetInstanceAsync(processId);
            if (process == null)
            {
                Console.WriteLine("Process not found!");
                return;
            }

            // Display current status
            Console.WriteLine($"\n=== Process Status: {process.Status} ===");
            Console.WriteLine($"Execution Time: {process.ExecutionTime}");

            // Display recent context messages
            DisplayRecentMessages(process.Context);

            // Handle different statuses
            switch (process.Status)
            {
                case InteractionProcessStatus.Finished:
                    Console.WriteLine("\n=== DISCUSSION COMPLETE ===");
                    DisplayFinalOutput(process.Output);
                    return;

                case InteractionProcessStatus.Error:
                    Console.WriteLine($"\n=== ERROR ===\n{process.FailureDetails}");
                    return;

                case InteractionProcessStatus.Killed:
                    Console.WriteLine("\n=== PROCESS TERMINATED ===");
                    return;

                case InteractionProcessStatus.HumanInteractionRequired:
                    await HandleHumanInteraction(client, process, humanName, humanId);
                    break;

                default:
                    Console.WriteLine("Waiting for agents...");
                    break;
            }

            // Poll interval
            await Task.Delay(TimeSpan.FromSeconds(5));
        }
    }

    private static void DisplayRecentMessages(InteractionProcessContext context)
    {
        var allMessages = context.All
            .OrderByDescending(m => m.Sequence)
            .Take(5)
            .Reverse()
            .ToList();

        if (allMessages.Count == 0) return;

        Console.WriteLine("\n--- Recent Messages ---");
        foreach (var message in allMessages)
        {
            var author = GetMessageAuthor(message);
            var content = GetMessageContent(message);
            var type = GetMessageType(message);

            Console.WriteLine($"[{type}] {author}:");
            Console.WriteLine($"  {Truncate(content, 200)}");
            Console.WriteLine();
        }
    }

    private static string GetMessageAuthor(InteractionProcessContextItem item) => item switch
    {
        InteractionProcessContextAgentMessage m => m.AgentName,
        InteractionProcessContextHumanMessage m => m.AuthorName,
        InteractionProcessContextSolutionProposal m => m.AgentName,
        InteractionProcessContextVoteProposal m => m.AgentName,
        InteractionProcessContextDirectQuestionAsk m => m.FromAgentName,
        InteractionProcessContextDirectQuestionAnswer m => m.AgentName,
        InteractionProcessContextOrchestratorMessage => "Orchestrator",
        _ => "System"
    };

    private static string GetMessageContent(InteractionProcessContextItem item) => item switch
    {
        InteractionProcessContextAgentMessage m => m.Message,
        InteractionProcessContextHumanMessage m => m.Message,
        InteractionProcessContextSolutionProposal m => m.Solution,
        InteractionProcessContextVoteProposal m => $"Proposes vote: {m.Proposal}",
        InteractionProcessContextDirectQuestionAsk m => $"[To {m.ToAgentName}] {m.Question}",
        InteractionProcessContextDirectQuestionAnswer m => m.Answer,
        InteractionProcessContextOrchestratorMessage m => m.Message,
        _ => ""
    };

    private static string GetMessageType(InteractionProcessContextItem item) => item switch
    {
        InteractionProcessContextAgentMessage => "Message",
        InteractionProcessContextHumanMessage => "Human",
        InteractionProcessContextSolutionProposal => "Solution",
        InteractionProcessContextVoteProposal => "Vote",
        InteractionProcessContextDirectQuestionAsk => "Question",
        InteractionProcessContextDirectQuestionAnswer => "Answer",
        InteractionProcessContextOrchestratorMessage => "System",
        _ => "Other"
    };

    private static async Task HandleHumanInteraction(
        IMaitentoClient client,
        InteractionProcess process,
        string humanName,
        Guid humanId)
    {
        Console.WriteLine("\n=== YOUR TURN ===");
        Console.WriteLine("Options:");
        Console.WriteLine("  1. Add a message");
        Console.WriteLine("  2. Vote YES on current proposal");
        Console.WriteLine("  3. Vote NO on current proposal");
        Console.WriteLine("  4. Skip (let agents continue)");
        Console.Write("\nChoice (1-4): ");

        var choice = Console.ReadLine()?.Trim();

        switch (choice)
        {
            case "1":
                Console.Write("Enter your message: ");
                var message = Console.ReadLine();
                if (!string.IsNullOrWhiteSpace(message))
                {
                    await client.InteractionProcesses.AddMessageAsync(
                        id: process.Id,
                        messageAuthorName: humanName,
                        messageAuthorId: humanId,
                        message: message
                    );
                    Console.WriteLine("Message sent!");
                }
                break;

            case "2":
                await client.InteractionProcesses.VoteYesAsync(
                    id: process.Id,
                    messageAuthorName: humanName,
                    messageAuthorId: humanId
                );
                Console.WriteLine("Voted YES!");
                break;

            case "3":
                Console.Write("Reason for voting NO: ");
                var reason = Console.ReadLine();
                if (!string.IsNullOrWhiteSpace(reason))
                {
                    await client.InteractionProcesses.VoteNoAsync(
                        id: process.Id,
                        messageAuthorName: humanName,
                        messageAuthorId: humanId,
                        reason: reason
                    );
                    Console.WriteLine("Voted NO!");
                }
                break;

            case "4":
                Console.WriteLine("Skipping...");
                break;

            default:
                Console.WriteLine("Invalid choice, skipping...");
                break;
        }
    }

    private static void DisplayFinalOutput(string? output)
    {
        if (string.IsNullOrEmpty(output))
        {
            Console.WriteLine("No output produced.");
            return;
        }

        try
        {
            var result = JsonSerializer.Deserialize<ReviewResult>(output, new JsonSerializerOptions
            {
                PropertyNameCaseInsensitive = true
            });

            if (result != null)
            {
                Console.WriteLine($"\nRecommendation: {result.Recommendation}");
                Console.WriteLine($"Confidence: {result.Confidence}");
                Console.WriteLine($"\nSummary:\n{result.Summary}");

                if (result.Strengths?.Length > 0)
                {
                    Console.WriteLine("\nStrengths:");
                    foreach (var s in result.Strengths)
                        Console.WriteLine($"  + {s}");
                }

                if (result.Weaknesses?.Length > 0)
                {
                    Console.WriteLine("\nWeaknesses:");
                    foreach (var w in result.Weaknesses)
                        Console.WriteLine($"  - {w}");
                }

                if (result.Conditions?.Length > 0)
                {
                    Console.WriteLine("\nConditions:");
                    foreach (var c in result.Conditions)
                        Console.WriteLine($"  * {c}");
                }
            }
        }
        catch
        {
            Console.WriteLine($"Raw output:\n{output}");
        }
    }

    private static string Truncate(string text, int maxLength) =>
        text.Length <= maxLength ? text : text[..maxLength] + "...";
}

public class ReviewResult
{
    public string Recommendation { get; set; } = "";
    public string Confidence { get; set; } = "";
    public string Summary { get; set; } = "";
    public string[]? Strengths { get; set; }
    public string[]? Weaknesses { get; set; }
    public string[]? Conditions { get; set; }
}

Step 5: Understanding the Discussion Flow

Turn Order

In a RoundRobin interaction with HumanChatMode.GoesLast, each round proceeds as follows:

Round 1:
  1. Researcher speaks (initial analysis)
  2. Critic speaks (identifies concerns)
  3. Summarizer speaks (synthesizes)
  4. Human speaks (optional guidance)

Round 2:
  1. Researcher responds (addresses concerns)
  2. Critic responds (re-evaluates)
  3. Summarizer responds (updates synthesis)
  4. Human speaks (optional)

... continues until consensus or timeout

Agent Actions

Agents can take the following actions during their turn:

ActionDescriptionWhen Used
MessageRegular contribution to discussionDefault communication
Direct QuestionAsk a specific agent a questionClarify another agent’s position
Solution ProposalPropose a final solutionWhen consensus seems possible
Vote ProposalInitiate a vote on a solutionTo formalize agreement
No ActionSkip turn (if allowed)Nothing to add

Voting Process

When an agent proposes a vote:

  1. The orchestrator pauses regular turns
  2. All participants (agents + human if configured) cast votes
  3. Votes can be:
    • Yes - Accept the proposal
    • No - Reject with reason (triggers continued discussion)
  4. If unanimous Yes, the solution is finalized
  5. If any No votes, discussion continues addressing concerns

Step 6: Monitoring with Shell Commands

You can also monitor and interact with running processes using the Maitento Shell:

# List active processes in the namespace
> interaction-ps --namespace research

# Attach to a running process with streaming output
> interaction-attach 550e8400-e29b-41d4-a716-446655440000 --stream

# Add a message as a human participant
> interaction-add-message 550e8400-... "Dr. Jane Smith" a1b2c3d4-... "Consider also the data privacy implications"

# Vote on a proposal
> interaction-vote-yes 550e8400-... "Dr. Jane Smith" a1b2c3d4-...
> interaction-vote-no 550e8400-... "Dr. Jane Smith" a1b2c3d4-... "Methodology needs refinement"

# Set default author for simplified commands
> interaction-author-defaults-set "Dr. Jane Smith" a1b2c3d4-e5f6-7890-abcd-ef1234567890
> interaction-vote-yes 550e8400-...

Step 7: Advanced Configuration

Customizing Voting Thresholds

For complex review panels, you might want to adjust consensus requirements. Currently, RoundRobin requires unanimous agreement. Future versions will support configurable thresholds.

Adding External Operations

Agents can be enhanced with external operations for tasks like:

  • Looking up prior research in a database
  • Fetching impact metrics from citation services
  • Checking budget against funding limits
var researcherWithOps = new InteractionAgentBinding(
    agent: new VersionReference(agents["researcher"].Id, version: 1),
    name: "Researcher",
    externalOperationsMcp: new[]
    {
        new McpExternalOperationBinding
        {
            Name = "search-papers",
            ExternalOperationId = paperSearchOperationId,
            PromptGuidance = "Use this to find related published research"
        }
    },
    externalOperationsOpenApi: Array.Empty<OpenApiExternalOperationBinding>()
);

Custom System Prompts

Add interaction-level guidance that applies to all agents:

systemPrompts: new[]
{
    "Focus on patient safety implications for all medical research.",
    "Consider both short-term results and long-term societal impact.",
    "Prioritize proposals that address underserved populations."
}

Example Output

Here’s what a typical discussion might look like:

=== Process Status: Running ===
Execution Time: 00:02:34

--- Recent Messages ---
[Message] Researcher:
  The proposal demonstrates strong scientific merit. The multi-modal approach
  combining CT, MRI, and pathology data is innovative and addresses known
  limitations of single-modality systems...

[Message] Critic:
  While the approach is promising, I have concerns about the 95% sensitivity
  claim. The validation dataset of 50,000 patients, while substantial, may
  not adequately represent rare cancer subtypes...

[Message] Summarizer:
  Key points so far: Both reviewers acknowledge the innovation, but there
  are concerns about statistical power for rare cancers and the aggressive
  timeline for clinical integration...

[Solution] Summarizer:
  Recommendation: Revise. The proposal has merit but needs: 1) Expanded
  power analysis for rare cancers, 2) Extended Phase 4 timeline...

=== YOUR TURN ===
Options:
  1. Add a message
  2. Vote YES on current proposal
  3. Vote NO on current proposal
  4. Skip (let agents continue)

Best Practices

Agent Design

  1. Distinct Roles: Give each agent a clear, non-overlapping responsibility
  2. Complementary Expertise: Design agents to cover different aspects of evaluation
  3. Constructive Framing: Even critics should aim to improve, not just reject

Interaction Configuration

  1. Enable Direct Questioning: Allows agents to clarify each other’s positions
  2. Set Reasonable Timeouts: Balance thoroughness with time constraints
  3. Configure Human Participation: Match the workflow to your approval process

Monitoring

  1. Stream Output: Use --stream flag for real-time visibility
  2. Track Context: Review the full discussion history, not just recent messages
  3. Intervene Thoughtfully: Human messages should add value, not just agree

Troubleshooting

Process Stuck in β€œRunning”

  • Check maximumNoActionLoops - may need to increase if agents legitimately have nothing to add
  • Verify agents have clear instructions for proposing solutions
  • Consider if durationAutoTermination is set appropriately

Agents Not Reaching Consensus

  • Review system prompts for conflicting instructions
  • Ensure Summarizer agent is prompted to propose solutions
  • Consider adding explicit consensus-building guidance

Human Input Not Being Processed

  • Verify humanSettings is configured in the interaction
  • Check that includeInVotes and includeInSolutions match your workflow
  • Ensure process status is HumanInteractionRequired before sending input

Next Steps