DEV Community

Solon Framework
Solon Framework

Posted on

How We Automated Supply Chain Anomaly Management with Solon TeamAgent

Building a Supply Chain Monitoring Agent with Solon TeamAgent

Our warehouse manager was spending 3 hours every morning checking inventory levels across 4 warehouses. By the time he finished the check, some items were already out of stock.

We needed something that could monitor inventory, detect anomalies, and coordinate replenishment automatically. The challenge was that this involved multiple different systems working together: inventory DB, supplier API, order forecasting, and exception handling.

Solon's TeamAgent turned out to be a perfect fit for this kind of multi-system coordination.

Why TeamAgent Instead of ReActAgent?

ReActAgent is a single AI agent that thinks and acts sequentially. For our supply chain scenario, we had several distinct responsibilities:

Agent Responsibility Tools
Inventory Monitor Periodically check stock levels get_stock_level(), get_lead_time()
Order Forecaster Predict demand based on history get_sales_trend(), get_seasonal_factor()
Replenishment Coordinator Generate purchase orders calc_reorder_qty(), create_po()
Exception Handler Escalate anomalies check_supplier_status(), notify_manager()

Each of these is a self-contained domain. Having one giant ReActAgent handle all of them would be messy. TeamAgent lets us define specialized sub-agents that work together through a collaboration protocol.

What Is TeamAgent?

From the official docs, TeamAgent is Solon's multi-agent coordination framework. Think of it as:

A team leader who assigns tasks to specialists and aggregates their results.

Each sub-agent (called a "member") is itself a ReActAgent with its own tools and identity. The TeamAgent decides which member should handle each part of a complex request.

Implementing the Supply Chain Monitor

1. Define Domain Tools

Each sub-agent needs its own tools. Here's the inventory monitor's tool:

public class InventoryTools extends AbsToolProvider {
    @ToolMapping(description = "Get current stock level for a specific SKU at a warehouse")
    public String get_stock_level(
            @Param(description = "Warehouse code, e.g. WH-SH, WH-BJ") String warehouse,
            @Param(description = "SKU identifier") String sku) {
        // In production, this queries the actual inventory DB
        return "{\"warehouse\":\"" + warehouse + "\", \"sku\":\"" + sku 
             + "\", \"qty\": 85, \"minStock\": 100, \"maxStock\": 500}";
    }

    @ToolMapping(description = "Get replenishment lead time for a SKU")
    public String get_lead_time(@Param(description = "SKU identifier") String sku) {
        return "{\"sku\":\"" + sku + "\", \"leadTimeDays\": 7}";
    }
}

public class ReplenishmentTools extends AbsToolProvider {
    @ToolMapping(description = "Calculate reorder quantity based on current stock and forecast")
    public String calc_reorder_qty(
            @Param(description = "Current stock") int currentStock,
            @Param(description = "Min stock threshold") int minStock,
            @Param(description = "Daily forecast demand") int dailyDemand,
            @Param(description = "Lead time in days") int leadTimeDays) {
        int reorderQty = (dailyDemand * (leadTimeDays + 3)) - currentStock;
        return "{\"reorderQty\": " + Math.max(reorderQty, 0) + "}";
    }

    @ToolMapping(description = "Create a purchase order in the ERP system")
    public String create_po(
            @Param(description = "SKU") String sku,
            @Param(description = "Quantity to order") int qty,
            @Param(description = "Supplier code") String supplier) {
        return "{\"poNumber\": \"PO-2026-07-\" + System.currentTimeMillis() 
             + \", \"sku\": \"" + sku + "\", \"qty\": " + qty 
             + ", \"supplier\": \"" + supplier + "\", \"status\": \"submitted\"}";
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Build Sub-Agents

Each sub-agent is a standalone ReActAgent with its own role description and tools:

// Inventory Monitor Agent
ReActAgent inventoryAgent = ReActAgent.of(chatModel)
        .role("You are an inventory monitoring specialist. " +
              "Check stock levels and identify items below minimum threshold.")
        .defaultToolAdd(new InventoryTools())
        .build();

// Replenishment Coordinator Agent
ReActAgent replenishAgent = ReActAgent.of(chatModel)
        .role("You are a replenishment specialist. " +
              "Calculate optimal order quantities and create purchase orders.")
        .defaultToolAdd(new ReplenishmentTools())
        .build();

// Exception Handler Agent
ReActAgent exceptionAgent = ReActAgent.of(chatModel)
        .role("You are an exception handler. " +
              "Escalate critical supply chain issues to management.")
        .defaultToolAdd(new ExceptionTools())
        .build();
Enter fullscreen mode Exit fullscreen mode

3. Assemble the Team

// Build TeamAgent with the three sub-agents
TeamAgent supplyChainTeam = TeamAgent.of(chatModel)
        .addMember(inventoryAgent)
        .addMember(replenishAgent)
        .addMember(exceptionAgent)
        .protocol(TeamProtocol.SEQUENTIAL) // Execute members one by one
        .build();

// Execute a monitoring round
AgentSession session = InMemoryAgentSession.of("supply_chain_round_001");

String task = "Run daily inventory check for warehouse WH-SH. " +
              "Check SKU 'HDD-2TB', current stock is 85, min threshold is 100. " +
              "If below threshold, calculate reorder qty and create PO.";

String result = supplyChainTeam.prompt(task)
        .session(session)
        .call()
        .getContent();
Enter fullscreen mode Exit fullscreen mode

4. The Execution Flow

When we run this, TeamAgent orchestrates the work:

  1. TeamAgent receives the task and assigns it to the Inventory Monitor
  2. Inventory Monitor calls get_stock_level() → finds stock (85) below threshold (100)
  3. TeamAgent passes "below threshold, need replenishment" to Replenishment Coordinator
  4. Replenishment Coordinator calls calc_reorder_qty() and create_po() → PO created
  5. TeamAgent compiles the final report with all actions taken

Parallel Monitoring with TeamProtocol.NONE

For checking multiple warehouses simultaneously, we can use the NONE protocol (free mode) which lets all members work in parallel:

TeamAgent parallelTeam = TeamAgent.of(chatModel)
        .addMember(inventoryAgent)
        .protocol(TeamProtocol.NONE) // All members work independently
        .build();

String result = parallelTeam.prompt(
    "Check stock levels for all 4 warehouses: WH-SH, WH-BJ, WH-GZ, WH-CD")
        .call()
        .getContent();
Enter fullscreen mode Exit fullscreen mode

Each inventory agent instance checks one warehouse in parallel, cutting total time by ~75%.

Real Deployment Notes

The official TeamAgent docs mention several config options we found useful:

# application.yml
solon.ai.team:
  max_turns: 15        # Max total reasoning cycles
  timeout: 60000       # 60s timeout for team execution
  protocol: sequential # sequential or none
Enter fullscreen mode Exit fullscreen mode

We run this as a scheduled job every 2 hours:

@Scheduled(cron = "0 0 */2 * * ?")
public void scheduledInventoryCheck() {
    String result = supplyChainTeam.prompt(
        "Run routine inventory check. Report any items below threshold " +
        "and auto-create replenishment POs where possible.")
        .call()
        .getContent();

    // Log results and alert on exceptions
    log.info("Supply chain check complete: {}", result);
}
Enter fullscreen mode Exit fullscreen mode

Results

After deploying this setup:

  • Inventory checks that previously took 3 hours of manual work now run in under 30 seconds
  • Replenishment POs are auto-generated when stock dips below threshold
  • Exception handler only escalates truly unusual cases (supplier delays, system errors)
  • The team can focus on strategic work instead of routine monitoring

All code based on Solon v4.0.3. Real API references: article/1283 (TeamAgent), article/1296 (TeamAgent config), article/1299 (NONE protocol).

Top comments (0)