---
title: "Manager scripts driving many objects from one system"
description: "You have a crowd, a swarm, a field of pickups, or a wave of enemies that all behave the same. Put ONE manager script on ONE entity that drives the whole cohort, instead of attaching a script to every member. This is the default shape for a homogeneous population."
engineVersion: v1.0.234
date: 2026-09-28
license: "(c) Gessa, proprietary. Cite with attribution to https://gessa.ai/docs/. Terms: https://gessa.ai/terms/."
canonical: https://gessa.ai/docs/knowledge/playbooks/manager-scripts-and-cohorts/
---

# Manager scripts driving many objects from one system

## When to use

You have a crowd, a swarm, a field of pickups, or a wave of enemies that all
behave the same. Put ONE manager script on ONE entity that drives the whole
cohort, instead of attaching a script to every member. This is the default shape
for a homogeneous population.

## The recipe

Author with `project_apply_script_semantic_patch` using the `ai_safe` op pack.
The manager is an entity with a `ScriptComponent`.

1. Tag the cohort: give every member a shared tag (for example `mob`) so one
   query finds them all.
2. Enumerate on a beat: `addTickHandler` `{ "handlerKey":"onManage" }` (or a
   coarser `addTimerHandler` for slow updates). Inside, `findEntitiesByTag`
   `{ "tag":"mob", "assignTo":"mobs", "limit": 64 }`, or use the paged
   `ctx.world.query` in a GessaScript body for larger sets (pass its `nextCursor`
   back verbatim to page).
3. Act on each member: `forEachEntityInResult` `{ "arrayRef":"mobs" }` whose body
   uses only the loop-intent ops (`despawnEntity`, `emitCustomEvent`,
   `incrementNumericState`, `attachComponentWithValidatedDefaults`). For richer
   per-member work (move, retarget), iterate in a GessaScript body and call
   `ctx.entity.patchComponent` on each.
4. Spawn and cull centrally: keep the population at a target size by
   `spawnEntityFromTemplate` when short and `despawnEntity` the excess or the
   dead, all from the one manager.
5. Shared state: hold cohort-level counters (alive count, wave number) in durable
   state with `incrementNumericState` so the manager and the HUD read one truth.

## Pitfalls

- `forEachEntityInResult.body` accepts ONLY the four loop-intent ops; anything
  richer must run in a `replaceScriptFromGessaScript` body iterating the result.
- Bound every scan: cap `findEntitiesByTag` `limit` and `forEachEntityInResult`
  `maxIterations` (both cap at 1000) and page `ctx.world.query` rather than
  pulling everything each tick.
- The collision and per-tick budgets cap at 1000 entities; a manager that keeps
  spawning without culling will hit the ceiling.
- Run the manager on the authority; do not also run a client copy or the cohort
  double-updates.

## Verify

- `simulation_run` (`qa.run.start`) to watch the manager spawn, update, and cull
  the cohort over several ticks.
- `project_get_graph_snapshot` to confirm the single manager `ScriptComponent`
  and its tick or timer handler.
