Skip to content
This repository was archived by the owner on Sep 11, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- feat: add kubernetes secrets provider and API to read secrets [#885](https://github.com/hypermodeinc/modus/pull/885)
- feat: "start" and "stop" are now mutation prefixes [#889](https://github.com/hypermodeinc/modus/pull/889)
- fix: start agents synchronously, and add examples setting data on start [#890](https://github.com/hypermodeinc/modus/pull/890)

## 2025-06-10 - Runtime 0.18.0-alpha.6

Expand Down
20 changes: 13 additions & 7 deletions runtime/actors/actorsystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,15 @@ func loadAgentActors(ctx context.Context, plugin *plugins.Plugin) error {
actors := _actorSystem.Actors()
runningAgents := make(map[string]bool, len(actors))
for _, pid := range actors {
if actor, ok := pid.Actor().(*wasmAgentActor); ok {
runningAgents[actor.agentId] = true
actor.plugin = plugin
if err := pid.Restart(ctx); err != nil {
logger.Err(ctx, err).Msgf("Failed to restart actor for agent %s.", actor.agentId)
go func(f_ctx context.Context, f_pid *goakt.PID) {
if actor, ok := f_pid.Actor().(*wasmAgentActor); ok {
runningAgents[actor.agentId] = true
actor.plugin = plugin
if err := f_pid.Restart(f_ctx); err != nil {
logger.Err(f_ctx, err).Msgf("Failed to restart actor for agent %s.", actor.agentId)
}
}
}
}(ctx, pid)
}

// spawn actors for agents with state in the database, that are not already running
Expand All @@ -112,7 +114,11 @@ func loadAgentActors(ctx context.Context, plugin *plugins.Plugin) error {
host := wasmhost.GetWasmHost(ctx)
for _, agent := range agents {
if !runningAgents[agent.Id] {
spawnActorForAgentAsync(host, plugin, agent.Id, agent.Name, false)
go func(f_ctx context.Context, agentId string, agentName string) {
if _, err := spawnActorForAgent(host, plugin, agentId, agentName, false); err != nil {
logger.Err(f_ctx, err).Msgf("Failed to spawn actor for agent %s.", agentId)
}
}(ctx, agent.Id, agent.Name)
}
}

Expand Down
21 changes: 8 additions & 13 deletions runtime/actors/agents.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,26 +67,21 @@ func StartAgent(ctx context.Context, agentName string) (*AgentInfo, error) {

agentId := xid.New().String()
host := wasmhost.GetWasmHost(ctx)
spawnActorForAgentAsync(host, plugin, agentId, agentName, true)
pid, err := spawnActorForAgent(host, plugin, agentId, agentName, true)
if err != nil {
return nil, fmt.Errorf("error spawning actor for agent %s: %w", agentId, err)
}

actor := pid.Actor().(*wasmAgentActor)
info := &AgentInfo{
Id: agentId,
Name: agentName,
Status: AgentStatusStarting,
Id: actor.agentId,
Name: actor.agentName,
Status: actor.status,
}

return info, nil
}

func spawnActorForAgentAsync(host wasmhost.WasmHost, plugin *plugins.Plugin, agentId, agentName string, initializing bool) {
// We spawn the actor in a goroutine to avoid blocking while the actor is being spawned.
// This allows many agents to be spawned in parallel, if needed.
// Errors are logged but not returned, as the actor system will handle them.
go func() {
_, _ = spawnActorForAgent(host, plugin, agentId, agentName, initializing)
}()
}

func spawnActorForAgent(host wasmhost.WasmHost, plugin *plugins.Plugin, agentId, agentName string, initializing bool) (*goakt.PID, error) {
// The actor needs to spawn in its own context, so we don't pass one in to this function.
// If we did, then when the original context was cancelled or completed, the actor initialization would be cancelled too.
Expand Down
10 changes: 10 additions & 0 deletions sdk/assemblyscript/examples/agents/assembly/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ export function startCounterAgent(): AgentInfo {
return agents.start("Counter");
}

/**
* Starts a counter agent with an initial count value,
* and returns info including its ID and status.
*/
export function startCounterAgentWithData(initialCount: i32): AgentInfo {
const info = agents.start("Counter");
updateCountAsync(info.id, initialCount);
return info;
}

/**
* Stops the specified agent by ID, returning its status info.
* This will terminate the agent, and it cannot be resumed or restarted.
Expand Down
12 changes: 12 additions & 0 deletions sdk/go/examples/agents/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ func StartCounterAgent() (agents.AgentInfo, error) {
return agents.Start("Counter")
}

// Starts a counter agent with an initial count value,
// and returns info including its ID and status.
func StartCounterAgentWithData(initialCount int) (agents.AgentInfo, error) {
info, err := agents.Start("Counter")
if err != nil {
return agents.AgentInfo{}, err
}

err = UpdateCountAsync(info.Id, initialCount)
return info, err
}

// Stops the specified agent by ID, returning its status info.
// This will terminate the agent, and it cannot be resumed or restarted.
// However, a new agent with the same name can be started at any time.
Expand Down
Loading