Supervisor¶
Monitors child agents and supervisors. Applies restart strategies on failure.
See Supervision for a full guide.
civitas.supervisor.Supervisor(name, children=None, strategy='ONE_FOR_ONE', max_restarts=3, restart_window=60.0, backoff='CONSTANT', backoff_base=1.0, backoff_max=60.0)
¶
Bases: AgentProcess
Manages child processes with restart strategies.
When a child crashes, the supervisor applies the configured restart strategy. If max_restarts is exceeded within restart_window, the supervisor escalates to its parent or stops permanently.
v0.9.0 E4 (D6, design supervision-endgame.md §6): a Supervisor is now
itself an actor — addressable, registered (SUPERVISOR_CAPABILITY),
with its own mailbox and message loop. Phase A (this constructor + the
start/stop ordering below) is a zero-behavior-change skeleton: crash
events still flow through the pre-E4 queue/drain-task mechanism until
Phase B swaps the control plane onto the mailbox. Public constructor
signature is unchanged — the actorization is purely internal.
Source code in civitas/supervisor.py
start()
async
¶
Start all children and begin monitoring them.
Source code in civitas/supervisor.py
stop()
async
¶
Stop all children gracefully.
v0.9.0 E4 (D-E4-6, found during Phase A implementation): this
INTENTIONALLY shadows AgentProcess.stop(name, drain, timeout) (the
soft-stop-a-dynamic-child API). The two are unrelated operations that
happen to share a name now that Supervisor is an AgentProcess. This is
safe: the inherited method requires self._dynamic_supervisor_name
to be wired, and Runtime's _wire_dyn_sup never sets it on a
Supervisor node (only recurses through its children) — so the
shadowed method could only ever have raised SpawnError on a
Supervisor instance. Pre-existing public API (sup.stop(), no args)
takes precedence over the newly-inherited one; renaming either public
method would be the breaking change, not keeping this override.
D-E4-8 (v0.9.0 E4 Phase B, correcting D-E4-3): the own loop stops
FIRST here, not last. Crash-triggered restarts (including the backoff
asyncio.sleep) now run on this same loop; only cancelling it —
which self._stop()'s own timeout-then-cancel fallback does — can
abort a restart already asleep in backoff. Stopping it last (as Phase
A did, safely, while the mailbox was inert) would let a crash's
backoff complete and resurrect a child mid-teardown, once cumulative
child-stop time exceeds the backoff delay. This restores exact parity
with the pre-E4 "cancel crash-drain before touching children"
guarantee, via the mechanism every other AgentProcess already gets.
Source code in civitas/supervisor.py
add_remote_child(name, heartbeat_interval=5.0, heartbeat_timeout=2.0, missed_heartbeats_threshold=3)
¶
Register a remote child for heartbeat-based monitoring.
Remote children are agents running in a Worker process. They are monitored via periodic heartbeat pings instead of task callbacks.
Source code in civitas/supervisor.py
all_agents()
¶
Recursively collect all AgentProcess instances in the tree.
Source code in civitas/supervisor.py
all_supervisors()
¶
Recursively collect all Supervisor instances (including self).
Source code in civitas/supervisor.py
civitas.supervisor.RestartStrategy
¶
Bases: Enum
Strategy used by a Supervisor when a child process crashes.
civitas.supervisor.BackoffPolicy
¶
Bases: Enum
Delay strategy applied between successive restart attempts.
DynamicSupervisor¶
Starts empty. Children are added and removed at runtime via self.spawn() / self.despawn(). Always uses ONE_FOR_ONE. See Dynamic supervision for a full guide.
civitas.supervisor.DynamicSupervisor(name, max_children=None, max_total_spawns=None, restart='transient', max_restarts=3, restart_window=60.0, spawner_allowlist=None, max_children_per_spawner=None, max_total_spawns_per_spawner=None, **kwargs)
¶
Bases: AgentProcess
Dynamic supervisor — starts empty, children added at runtime via spawn().
Declared as a static child in topology YAML under type: dynamic_supervisor.
Only its children change at runtime. Enforces ONE_FOR_ONE restart semantics —
no escalation to parent on restart exhaustion; fires on_child_terminated instead.
Agents call self.spawn() / self.despawn() / self.stop() to manage children. All requests travel as bus messages (civitas.dynamic.*) so the same API works in-process (v0.4) and cross-process (v0.5).
spawner_allowlist (optional) restricts who may spawn children here: when a
set is given, a spawn whose spawner is not in it is rejected before the
on_spawn_requested hook runs. Default None keeps the open behavior. It is
the built-in authorization control for cross-tree spawn_into (D8).
max_children_per_spawner / max_total_spawns_per_spawner (optional, R5) cap a
single spawner's concurrent and lifetime spawns, in addition to the supervisor-wide
max_children / max_total_spawns. Default None is unbounded per spawner.
Source code in civitas/supervisor.py
on_spawn_requested(agent_class, name, config)
async
¶
Governance veto hook. Return False to deny the spawn request.
Default implementation approves all requests. Subclass to enforce
allowlists, rate limits, or policy checks. Read :attr:current_spawner
inside this hook to authorize by the requesting agent's name.
Source code in civitas/supervisor.py
all_dynamic_agents()
¶
on_stop()
async
¶
Cancel all dynamic children on shutdown.
Source code in civitas/supervisor.py
civitas.supervisor.RestartMode
¶
Bases: Enum
Restart policy for dynamic children.