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 4 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
18 changes: 12 additions & 6 deletions runtime/actors/actorsystem.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,11 +93,13 @@ func loadAgentActors(ctx context.Context, plugin *plugins.Plugin) error {
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() {
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)
}
}()
}
}

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() {
if _, err := spawnActorForAgent(host, plugin, agent.Id, agent.Name, false); err != nil {
logger.Err(ctx, err).Msgf("Failed to spawn actor for agent %s.", agent.Id)
}
}()
}
}

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