Managing agent lifecycle: the state machine, transitions, and GenStateMachine internals.
Every governed agent has a lifecycle. The state machine ensures agents cannot skip steps — you cannot run an agent that has not been enabled, and you cannot remove an agent that is still running. This prevents orphaned processes, ungoverned execution, and permission leaks.
installed --> enabled --> running
^ |
| v
+--- enabled (stop)
|
v
disabled --> removed
| State | Meaning | Process Running? |
|---|---|---|
installed | Registered with permissions, awaiting activation | No |
enabled | Supervised process started | Yes (idle or working) |
running | Actively executing a task | Yes |
disabled | Process stopped, can be re-enabled | No |
removed | Fully deregistered, permissions cleared | No |
| From | To | Trigger | Tool |
|---|---|---|---|
installed | enabled | Operator activates agent | agent_enable |
enabled | running | Agent begins work | Automatic |
running | enabled | Agent completes work | Automatic |
enabled | disabled | Operator stops agent | agent_disable |
running | disabled | Operator force-stops agent | agent_disable(force: true) |
disabled | enabled | Operator re-activates | agent_enable |
disabled | removed | Operator deregisters | agent_remove |
Invalid transitions (e.g., installed directly to running, or removed to any state) are rejected by the GenStateMachine.
{
"agent_id": "my-worker",
"name": "My Worker Agent",
"child_spec": {"module": "MyApp.Worker", "args": []},
"permissions": {
"granted": [
{"type": "filesystem", "access": "read", "resource": "/data/**"}
],
"denied": [
{"type": "filesystem", "access": "write", "resource": "/etc/**"}
]
},
"metadata": {
"version": "1.0.0",
"source": "agentelic"
}
}
PermissionEngine writes grants and denials to ETS
AgentSupervisor starts a new AgentLifecycle GenStateMachine
Initial state is set to :installed
AuditWriter logs the install event with full manifest
Explicit denials take precedence over grants. If you grant filesystem:read:/data/** but deny filesystem:read:/data/secrets/**, the denial wins for any path matching /data/secrets/**.
agent_enable(agent_id: "my-worker")
GenStateMachine validates the transition (must be in installed or disabled)
The wrapped child_spec is started under AgentSupervisor
State transitions to enabled
Audit entry is written
AutonomyController sets default level (observe) if not already set
agent_disable(agent_id: "my-worker")
Sends a shutdown signal to the wrapped process
Waits for configurable timeout (default 5 seconds)
If process exits cleanly, state transitions to disabled
Audit entry records graceful shutdown
agent_disable(agent_id: "my-worker", force: true)
Immediately kills the wrapped process (:kill signal)
State transitions to disabled
Audit entry records forced shutdown with reason
Each AgentLifecycle is a gen_statem process with the following structure:
State data: agent_id, child_spec, current PID (when running), metadata
Callbacks: handle_event/4 for each valid transition
Invalid transitions: return {:keep_state_and_data, []} with an error
logged to the audit trail
Crash handling: if the wrapped process crashes, the AgentLifecycle
transitions back to enabled (not disabled) and the DynamicSupervisor restart strategy applies
The most conservative pattern. Install the agent, enable it at observe level, review its recommendations in the audit trail, then gradually promote to advise and act.
agent_install(manifest) --> agent_enable(agent_id) --> review audit --> autonomy_level(advise) --> review --> autonomy_level(act)
Install, enable, run a specific task, disable, remove. Useful for one-off jobs that should not persist.
agent_install --> agent_enable --> (task completes) --> agent_disable --> agent_remove
Disable the agent, update its child_spec or permissions, re-enable.
agent_disable --> (update config) --> agent_enable
agent_status(agent_id: "my-worker")
Returns:
{
"agent_id": "my-worker",
"state": "enabled",
"autonomy_level": "observe",
"permissions_granted": 3,
"permissions_denied": 1,
"installed_at": "2026-01-15T10:30:00Z",
"last_transition": "2026-01-15T10:31:00Z"
}