Skip to Content
Next — You're reading the unreleased preview docs. APIs and pages may change before the next release.Switch to the current docs →
How-To GuidesMoonBitCalling Another Agent (MoonBit)

Calling Another Agent (MoonBit)

Overview

The #derive.agent code generation tool auto-generates a <AgentName>Client struct for each agent, enabling agent-to-agent communication via RPC. An awaited call blocks the calling agent until the target agent returns a result.

Getting a Client (Scoped)

Use <AgentName>Client::scoped(...) with the target agent’s constructor parameters and an async callback. Call it from an async fn; MoonBit has no await keyword. The client is automatically dropped when the callback returns:

CounterClient::scoped("my-counter", async fn(counter) { counter.increment() counter.increment() let value = counter.get_value() value })

This is the recommended pattern — it ensures the client resource is cleaned up automatically.

Note: agents with no constructor parameters omit the parameter from scoped / get.

Getting a Client (Manual)

Use <AgentName>Client::get(...) for manual lifecycle management. You must call client.drop() when done:

let counter = CounterClient::get("my-counter") counter.increment() let value = counter.get_value() counter.drop() // must call drop when done

This does not create the agent — the agent is created implicitly on its first invocation. If it already exists, you get a handle to the existing instance.

Awaited Call

Call a method and block until the result returns:

CounterClient::scoped("my-counter", async fn(counter) { counter.increment() let count = counter.get_value() count })

The calling agent blocks until the target agent processes the request and returns. This is the standard RPC pattern.

Passing Complex Types

Agent methods accept custom types defined in your agent code:

TaskManagerClient::scoped(async fn(tm) { let count = tm.add_task({ title: "Build RPC support", priority: High, description: Some("Implement agent-to-agent communication"), }) let high_tasks = tm.get_by_priority(High) let _ = high_tasks count })

Phantom Agents

To create multiple distinct instances with the same constructor parameters, use phantom agents. See the golem-multi-instance-agent-moonbit guide.

Cross-Component RPC

MoonBit does not currently support putting an agent definition in a shared package and importing its generated client from two separate components. #derive.agent describes the concrete implementation type, and golem_sdk_tools agents emits the registration, dispatch, and client code together in the component package.

Use an internal guest bridge instead. The provider must build before the bridge exists. This example uses a TypeScript provider example:weather containing WeatherAgent and a MoonBit caller:

components: example:weather: dir: weather templates: ts example:caller: dir: caller templates: moonbit dependencies: agents: - example:weather/WeatherAgent

golem build generates golem-temp/bridge-sdk/moonbit/internal/weather-agent-guest-client. The generated module contains moon.mod and moon.work; do not edit these generated files.

Add the generated module directory to the application’s moon.work, preserving any existing local SDK member:

members = [ ".", "golem-temp/bridge-sdk/moonbit/internal/weather-agent-guest-client", ]

Add "weather-agent-guest-client@0.0.1" to the imports in the application’s moon.mod. The workspace resolves that module locally. The stock Golem MoonBit template uses workspace artifact paths based on the application name, so the caller needs no custom build or embed commands. Component namespaces do not need to match the application name.

For a MoonBit provider, use an independently buildable module and workspace. A provider in the caller’s workspace cannot resolve a bridge member that has not been generated yet.

Import the generated client package from the caller’s moon.pkg:

import { "weather-agent-guest-client/client" @weather, // ...the caller's Golem SDK imports }

Then call the provider through the generated typed client:

pub async fn ExampleAgent::weather_in_london(self : Self) -> String { let _ = self @weather.WeatherAgentClient::scoped( "London", async fn(remote) { remote.current_weather() }, ) }

This client is generated from the built provider’s discovered schema before the caller is compiled. It uses the guest RPC host API directly, not the external REST bridge transport. Do not import the provider component’s executable MoonBit package into the caller.

Avoiding Deadlocks

Never create RPC cycles where A awaits B and B awaits A — this deadlocks both agents. Use trigger_ (fire-and-forget) to break cycles. See the golem-fire-and-forget-moonbit guide.