Building a Multi‑Agent AI System: Easy‑Agent’s Foreman, Coder, and Researcher

This article explains how the easy‑agent project evolved from a single monolithic AI into a multi‑agent architecture with specialized Foreman, Coder, and Researcher agents, covering design principles, communication mechanisms, task decomposition, fault tolerance, parallel execution, observability, and future extensions, complete with code examples and open‑source links.

Code Wrench
Code Wrench
Code Wrench
Building a Multi‑Agent AI System: Easy‑Agent’s Foreman, Coder, and Researcher

01 From Single Agent to Multi‑Dimensional Agent: Architectural Evolution

After adding streaming interaction, observability, and multimodal capabilities to easy-agent, a single all‑purpose agent behaves like a Swiss‑army knife—capable of many tasks but inefficient when switching between tool selection, code execution, and debugging. The solution is a collaborative system where each agent specializes in a domain:

Foreman : task decomposition and scheduling

Coder : code writing, execution, and debugging

Researcher : information retrieval and knowledge organization

# Multi‑Agent definition in configuration file
agents:
  foreman:
    role: "foreman"
    system_prompt: |
      You are a "Foreman" Agent. Your task is to decompose user requests and assign them to appropriate "worker" Agents.
    allowed_tools: [call_coder, call_researcher]

  coder:
    role: "coder"
    system_prompt: |
      You are a "Coder" Agent responsible for all code‑related tasks.
    allowed_tools: [run_code, read_file, write_file, git_cmd]

02 Core Mechanism: Communication Bridge Between Agents

The key to multi‑agent collaboration is a unified communication mechanism. Two core tools enable cross‑agent calls.

Agent Call Tool

type CallCoderTool struct{}

func (t *CallCoderTool) Run(ctx context.Context, argsJSON string, a *Agent, events <-chan StreamEvent) (string, error) {
    // Decode task description
    var args struct {
        Task struct {
            Description string `json:"description"`
        } `json:"task"`
    }
    // Get Coder Agent instance
    coderAgent := a.otherAgents["coder"]
    // Start sub‑agent streaming execution
    subAgentEvents := make(chan StreamEvent)
    go coderAgent.StreamRunWithSessionAndImages(ctx, args.Task.Description, "", nil, "", subAgentEvents)
    // Forward sub‑agent events to main channel
    for event := range subAgentEvents {
        events <- event // show sub‑agent thinking to user
    }
    return "", nil
}
Key Quote: "True collaboration is not simple task hand‑off, but transparent sharing of thought processes."

Seamless Event Stream Transfer

When Foreman calls Coder, every step—thinking, tool invocation, code output—is streamed to the user, creating the illusion of a single continuous agent.

03 Making Collaboration Smarter: Task Decomposition

Intelligent Task Judgment

system_prompt: |
  You are a "Foreman" Agent. Your task is to decompose user requests and assign them to appropriate "worker" Agents.

You can use the following tools:
- call_coder: invoke the Coder Agent for code writing, modification, review, and execution.
- call_researcher: invoke the Researcher Agent for web and knowledge‑base searches.

Please select and call the appropriate tool based on the nature of the task.

Concrete Collaboration Example

User request: "Help me write a Python crawler to scrape Douban Top 250 movies."

Foreman's reasoning:

Identify task type: code writing → call Coder.

Construct task description: translate user need into a concrete programming task.

Invoke Coder Agent.

call_coder(task="编写Python爬虫爬取豆瓣电影Top250数据")

Coder's execution:

Analyze technical solution: choose requests + BeautifulSoup.

Write code implementing the crawler.

Debug and test in a sandbox.

Return usable crawler code.

[Foreman] 正在思考如何响应...
[Foreman] 检测到代码任务,正在调用Coder Agent...
[Coder] 正在分析技术方案...
[Coder] 编写爬虫代码中...
[Coder] 在沙箱中运行代码...
[Coder] 代码运行成功!返回结果...

04 Fault Tolerance: Making Collaboration Reliable

Graceful Degradation Strategy

// Fault‑tolerance handling in CallCoderTool
for event := range subAgentEvents {
    if event.Type == "error" {
        Logger.Error().Str("coder_error", p.Message).Msg("Coder Agent failed")
        return fmt.Errorf("coder agent error: %s", p.Message)
    }
}

Retry and Fallback

Record error information.

Attempt alternative implementations.

If necessary, report the issue to the user with suggestions.

05 Performance Optimization: Parallel Execution

Concurrent Tool Invocation

func (a *Agent) handleToolCalls(ctx context.Context, toolCalls []ToolCall, sessionID string, events chan<- StreamEvent) []ChatMessage {
    var wg sync.WaitGroup
    toolResults := make(chan ChatMessage, len(toolCalls))
    for _, toolCall := range toolCalls {
        wg.Add(1)
        go func(tc ToolCall) {
            defer wg.Done()
            result := a.execTool(ctx, &fc, sessionID, events)
            toolResults <- result
        }(toolCall)
    }
    wg.Wait()
    close(toolResults)
    return nil
}

When Foreman needs both Coder and Researcher, they run in parallel, greatly improving efficiency.

06 User Experience: Seamless Collaborative Interaction

Continuous thinking process

Real‑time execution output

Unified final answer

Intelligent Role Labels

// Add agent role information to StreamEvent
events <- StreamEvent{
    Type: "thinking",
    Payload: ThinkingEventPayload{
        Text: fmt.Sprintf("[%s] %s", agentRole, thinkingText),
    },
}
[Foreman] 正在分析任务...
[Coder] 编写Python代码中...
[Researcher] 搜索相关信息中...

07 Monitoring and Debugging: Distributed Tracing

Distributed Tracing Integration

ctx, span := tracer.Start(ctx, "Agent.CallCoder",
    trace.WithAttributes(
        attribute.String("foreman_agent", a.role),
        attribute.String("target_agent", "coder"),
        attribute.String("task_description", args.Task.Description),
    ),
)
defer span.End()

Performance Dashboard

Execution time per agent

Call chain between agents

Precise bottleneck identification

08 Live Demo: Power of Collaboration

User request: "Write a Go REST API that fetches today’s hot news and generates API docs."

Foreman analyzes the composite task.

Calls Researcher: call_researcher("搜索今天的热门新闻") Researcher fetches news data.

Foreman decides to generate the API.

Calls Coder:

call_coder("用Go语言编写REST API,返回新闻数据,并生成文档")

Coder writes, tests, and generates Swagger documentation.

09 Architectural Evolution: Towards an Extensible Ecosystem

Scalable Agent Ecosystem

Tester Agent – dedicated to code testing.

Deployment Agent – handles deployment.

Monitor Agent – responsible for monitoring.

Agent Marketplace Concept

Standardized agent interfaces.

Version management for agents.

Capability descriptions for discoverability.

10 Summary

Intelligent task decomposition – Foreman accurately identifies and assigns tasks.

Specialized agents – each focuses on its domain.

Transparent collaboration – users see a continuous workflow.

Graceful fault tolerance – single failures don’t break the system.

Parallel execution – overall efficiency gains.

Full observability – every step is visible.

Future plans include shared agent memory, dynamic agent creation, and performance‑based agent selection.

Source code is fully open‑source:

GitHub: https://github.com/louis-xie-programmer/easy-agent

Gitee: https://gitee.com/louis_xie/easy-agent

AIobservabilityGoOpenTelemetryMulti-agentAgent architectureParallel Execution
Code Wrench
Written by

Code Wrench

Focuses on code debugging, performance optimization, and real-world engineering, sharing efficient development tips and pitfall guides. We break down technical challenges in a down-to-earth style, helping you craft handy tools so every line of code becomes a problem‑solving weapon. 🔧💻

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.