Phantom Agents in Scala
Phantom agents allow creating multiple distinct agent instances that share the same constructor parameters. Normally, an agent is uniquely identified by its constructor parameter values — calling get with the same parameters always returns the same agent. Phantom agents add an extra phantom ID (a UUID) to the identity, so you can have many independent instances with identical parameters.
Agent ID Format
A phantom agent’s ID appends the phantom UUID in square brackets:
agent-type(param1, param2)[a09f61a8-677a-40ea-9ebe-437a0df51749]A non-phantom agent ID has no bracket suffix:
agent-type(param1, param2)Creating and Addressing Phantom Agents (RPC)
The native definition-derived Scala SDK client extends AgentCompanionBase and provides the following synchronous factories. This section and its UUID recipes describe native SDK clients, not CLI-generated bridges.
| Method | Description |
|---|---|
Client.get(params...) | Get or create a non-phantom (durable) agent identified solely by its parameters |
Client.newPhantom(params...) | Select a fresh phantom invocation/instance according to the agent mode |
Client.getPhantom(params..., phantom) | Get or create a phantom agent with a specific UUID |
Note: Ephemeral agents only get getPhantom and newPhantom — they do not have a get method.
Example (Durable Counter)
import java.util.UUID
// Non-phantom: always the same agent for the same name
val counter = CounterClient.get("shared")
// New logical phantom proxy: instance creation happens on first invocation
val phantom1 = CounterClient.newPhantom("shared")
val phantom2 = CounterClient.newPhantom("shared")
// phantom1 and phantom2 are different agents, both with name="shared"
// Reconnect to an existing phantom by its UUID
val existingId: UUID = UUID.fromString("6ba7b810-9dad-11d1-80b4-00c04fd430c8")
val samePhantom = CounterClient.getPhantom("shared", existingId)
// Call methods on the phantom
phantom1.increment().map { result =>
println(s"counter value: $result")
}WithConfig Variants
If the agent has config fields, additional methods are generated (getWithConfig only for durable targets):
Client.getWithConfig(params..., configFields...)Client.newPhantomWithConfig(params..., configFields...)Client.getPhantomWithConfig(params..., phantom, configFields...)
Each generated config argument is positional Option[T] = None. Supply Some(value)
to override a field, and None to retain its configured value.
CLI-generated guest/JVM bridge clients do not extend AgentCompanionBase.
Durable bridges expose get, getPhantom, and newPhantom; ephemeral bridges
expose only newPhantom, a logical proxy whose final invocation identity is
assigned on invocation. Guest factories are synchronous; external JVM factories
return Future. See golem-call-from-external-scala.
Method Signature Differences
Note that in the Scala SDK, getPhantom takes the phantom UUID as the last parameter (after the constructor parameters), while newPhantom takes only the constructor parameters:
// getPhantom: constructor params first, then phantom UUID
CounterClient.getPhantom("counter-name", phantomUuid)
// newPhantom: constructor params only
CounterClient.newPhantom("counter-name")
// For DURABLE agents, newPhantom delegates with a generated idempotency key:
// def newPhantom(name: String): CounterRemote =
// getPhantom(name, HostApi.generateIdempotencyKey())Key Points
- Phantom identity and durability mode are independent. Durable phantom agents persist just like regular durable agents; ephemeral invocations discard their memory. If the executor restarts while preparing an ephemeral invocation, Golem can replay the recorded initialization so that the method can still start. Once the method starts, a failure is terminal; Golem does not replay the method.
- The phantom ID is a
java.util.UUID. - For durable targets,
newPhantomgenerates a UUID viaHostApi.generateIdempotencyKey(). For ephemeral targets it resolves without a phantom override; invocation metadata supplies the actual identity. Factory construction is synchronous in both cases. - For durable targets,
getPhantomwith the same UUID and parameters addresses the same persistent agent. For ephemeral targets it selects an invocation identity, not reusable persistent state. - Phantom and non-phantom agents with the same constructor parameters are different agents — they do not share state.
- Durable agents get
get,getPhantom, andnewPhantom; ephemeral agents get onlygetPhantomandnewPhantom.