Agent to Agent communication
See Agent communication for an overview of how agents call, wait for, and stream to each other.
Code-first approach
When defining an agent in code, this automatically defines a client interface for calling remote agents as well. This way an agent of an agent type can create other agents and invoke methods on them. The target agent can be of any agent type available for the caller agent in code.
The simplest way to achieve this is to keep all the agent types that need to call each other in the same component. For agents in different components, use a generated guest bridge instead of importing the target’s implementation.
Creating a client
The first step of calling a remote agent is creating a client for it by specifying which agent type it is and identifying the agent by providing values for its constructor parameters. If the agent identified by the parameters has not been existing yet, it is going to be created and its constructor is executed remotely. If it has been already created, the client just points to it and no action is taken until an agent method is called using it.
The examples below return illustrative weather data for the selected location. The caller returns the remote method’s result, rather than fetching a real weather service.
TypeScript
In the following example, we have two agents defined; a weather agent with a constructor parameter identifying a location, and another example agent creating and calling this weather agent.
import { z } from 'zod';
import { defineAgent, method } from '@golemcloud/golem-ts-sdk';
export const WeatherAgent = defineAgent({
name: 'WeatherAgent',
id: { location: z.string() },
methods: {
currentWeather: method({
input: {},
returns: z.string(),
description: "Gets the current weather",
}),
},
});
export const WeatherAgentImpl = WeatherAgent.implement({
init: ({ id }) => ({ location: id.location }),
methods: {
currentWeather() {
return `${this.location}: sunny, 18°C`;
},
},
});
export const ExampleAgent = defineAgent({
name: 'ExampleAgent',
id: {},
methods: {
run: method({ input: {}, returns: z.string() }),
},
});
export const ExampleAgentImpl = ExampleAgent.implement({
init: () => ({}),
methods: {
async run() {
const weatherInLondon = WeatherAgent.client.get({ location: "London" });
return await weatherInLondon.currentWeather({});
},
},
});Here weatherInLondon is the client for calling a remote WeatherAgent, obtained from WeatherAgent.client with the target agent’s id record - it exposes the agent methods, such as currentWeather as its own methods.
Calling an agent in another component
Declare the target agent in the caller component’s dependencies.agents. During golem build, Golem builds the provider, discovers its agent schema, generates a typed guest client in the caller’s language, and then builds the caller. The provider and caller can use different SDKs. You do not need to share or duplicate the agent definition.
The dependency names the provider component and agent type. When creating a client, pass the constructor values that identify the target agent, not a component ID or server address.
The examples below assume that the application already contains a component named example:weather. It exports the durable WeatherAgent from the earlier example: its constructor takes a location, and its weather method returns a string. Merge the caller configuration into the existing golem.yaml, then add the generated client to the caller’s build as shown in your SDK’s tab. Paths assume the default golem-temp directory at the application root.
TypeScript
Declare the dependency on the TypeScript caller:
components:
example:caller:
dir: caller
templates: ts
dependencies:
agents:
- example:weather/WeatherAgentAdd the generated source to the caller’s existing tsconfig.json. Merge these settings with the existing compiler options, path mappings, and included sources:
{
"compilerOptions": {
"paths": {
"weather-agent-guest-client": [
"../golem-temp/bridge-sdk/ts/internal/weather-agent-guest-client/weather-agent-guest-client.ts"
]
}
},
"include": [
"src/**/*.ts",
"../golem-temp/bridge-sdk/ts/internal/weather-agent-guest-client/*.ts"
]
}Call the generated client from an agent method, without importing or registering the provider’s definition:
import { WeatherAgent } from "weather-agent-guest-client"
export async function weatherInLondon(): Promise<string> {
const weather = WeatherAgent.get("London")
return await weather.currentWeather()
}Unlike a definition client, the generated client’s get takes positional constructor arguments and its weather method takes no input object. Resolution and invocation failures throw.
After adding the dependency and build integration, run golem build from the application root. Rebuild whenever the provider’s API changes; do not edit generated client files. The caller uses Golem’s agent-to-agent RPC, not the external REST bridge, so it needs no API server URL or access token. Deploy both components to the same environment before invoking the caller.
Phantom agents
Calling get gets or creates a durable agent identified by its constructor parameters. Durable agent types have a default instance and can have arbitrary phantom instances distinguished by an additional phantom ID UUID. Ephemeral clients expose only phantom factories.
When using agent IDs (for example in APIs or the CLI), phantom IDs are appended to the agent ID in square brackets:
- Primary instance of
WeatherAgentwith parameterLondon:WeatherAgent("London")
- A phantom instance of the same agent:
WeatherAgent("London")[43d9d7ab-e1d7-47e2-a213-d635eaa317a2]
Phantom agents are created through Agent-to-Agent communication or forking. To create a new phantom agent via RPC, use the alternative client constructor method:
TypeScript
const { client: phantomAgent, agentId, phantomId } = WeatherAgent.client.newPhantom({ location: "London" });If we know the phantom ID of an agent, we can create a client targeting an existing phantom agent:
TypeScript
const { client: phantomAgent, agentId, phantomId } = WeatherAgent.client.newPhantom({ location: "London" });
const samePhantomAgent = WeatherAgent.client.getPhantom({ location: "London" }, phantomId);Dynamic RPC
When the target agent type or method is selected at runtime, use Agent reflection. It supports discovering agent schemas, addressing phantom agents, and invoking methods dynamically without importing the target’s definition or generating a guest bridge.
Calling a remote agent method
Once we have a client for a remote agent, it is possible to call its methods just as if it would be a local instance.
TypeScript
const currentWeather = await weatherInLondon.currentWeather();Triggering execution of an agent method
It is possible to trigger the remote execution of an agent method without awaiting it. This is useful for spawning background tasks, for example.
TypeScript
To trigger an agent method and return immediately, use the trigger method exposed on each remote method in the client:
const remoteAgent = BackgroundTaskAgent.client.get({ jobId: backgroundJobId });
remoteAgent.runTask.trigger({ message: "hello", count: 1234 });Scheduling an agent method
An advanced case of triggering the execution of an agent method is to schedule it to be executed at a later time.
TypeScript
Similar to .trigger, there is a .schedule method as well on each remote agent method in the client:
const remoteAgent = BackgroundTaskAgent.client.get({ jobId: backgroundJobId });
remoteAgent.runTask.schedule({ seconds: 60n, nanoseconds: 0 }, { message: "hello", count: 1234 });Here the first parameter is a Datetime object, with seconds and nanoseconds fields representing the UNIX Epoch time when the method should be executed. In TypeScript, seconds is a bigint. The rest of the parameters are the same as for the original method.