Agent OS uses POSIX-inspired signals to control agent lifecycle. This document covers all signals, their behaviors, and edge cases.
| Signal | Value | Description | Maskable |
|---|---|---|---|
SIGSTOP |
1 | Pause execution (enter shadow mode) | ✅ |
SIGCONT |
2 | Resume execution | ✅ |
SIGINT |
3 | Graceful interrupt | ✅ |
SIGKILL |
4 | Immediate termination | ❌ |
SIGTERM |
5 | Request graceful shutdown | ✅ |
SIGUSR1 |
6 | Enter diagnostic mode | ✅ |
SIGUSR2 |
7 | Trigger checkpoint | ✅ |
SIGPOLICY |
8 | Policy violation (escalates to SIGKILL) | ❌ |
SIGTRUST |
9 | Trust boundary crossed | ❌ |
SIGBUDGET |
10 | Resource budget exceeded | ✅ |
SIGLOOP |
11 | Infinite loop detected | ✅ |
SIGDRIFT |
12 | Goal drift detected | ✅ |
Behavior: No-op. The signal is acknowledged but state doesn't change.
dispatcher.send_signal(AgentSignal.SIGSTOP) # Agent is now stopped
dispatcher.send_signal(AgentSignal.SIGSTOP) # No-op, already stoppedThe signal is still logged to the flight recorder for audit purposes.
Behavior: No-op. The signal is acknowledged but state doesn't change.
# Agent is running
dispatcher.send_signal(AgentSignal.SIGCONT) # No-op, already runningBehavior: The agent is stopped then resumed. There may be a brief pause.
dispatcher.send_signal(AgentSignal.SIGSTOP)
dispatcher.send_signal(AgentSignal.SIGCONT)
# Agent resumes immediatelyBehavior: The signal is logged but no exception is raised (agent already terminated).
dispatcher.send_signal(AgentSignal.SIGKILL) # Agent terminated
dispatcher.send_signal(AgentSignal.SIGKILL) # No-op, already deadBehavior: First SIGPOLICY escalates to SIGKILL. Subsequent signals are logged but agent is already terminated.
# Policy violation 1 → SIGPOLICY → SIGKILL → Agent terminated
# Policy violation 2 → SIGPOLICY → No-op (agent already dead)You can temporarily block signals during critical operations:
with dispatcher.mask_signals({AgentSignal.SIGINT, AgentSignal.SIGTERM}):
# SIGINT and SIGTERM are queued, not delivered
await critical_operation()
# Queued signals are delivered when mask is releasedNote: SIGKILL, SIGPOLICY, and SIGTRUST cannot be masked.
If a signal arrives while an action is executing:
- SIGSTOP: Action completes, then agent stops
- SIGKILL: Action is interrupted immediately (may leave partial state)
- SIGINT: Action completes, then agent stops (graceful)
Signals are processed in FIFO order. If you send:
dispatcher.send_signal(AgentSignal.SIGSTOP)
dispatcher.send_signal(AgentSignal.SIGCONT)SIGSTOP is always processed before SIGCONT.
- Masked signals (SIGSTOP, SIGINT, etc.): Exception is logged, agent continues
- Unmaskable signals (SIGKILL): Agent is terminated regardless
SIGSTOP
┌─────────────────────────────────────┐
│ │
▼ │
┌─────────┐ SIGCONT ┌─────────┐ │
│ STOPPED │◄─────────────►│ RUNNING │────┘
└─────────┘ └─────────┘
│ │
│ SIGKILL │
│ SIGTERM │
└──────────┬──────────────┘
│
▼
┌────────────┐
│ TERMINATED │
└────────────┘
You can register custom handlers for maskable signals:
def my_handler(info: SignalInfo) -> None:
print(f"Custom handling: {info.signal.name}")
dispatcher.register_handler(AgentSignal.SIGUSR1, my_handler)
dispatcher.send_signal(AgentSignal.SIGUSR1)
# Prints: "Custom handling: SIGUSR1"All signals are automatically logged to the Flight Recorder:
history = dispatcher.get_signal_history()
# [
# {"signal": "SIGSTOP", "timestamp": "...", "source": "user", ...},
# {"signal": "SIGCONT", "timestamp": "...", "source": "user", ...},
# ]- Kernel Internals - How the kernel processes signals
- Security Spec - Policy violation escalation
- Troubleshooting - Common signal-related issues