The prompt injection looks like this at crisis:
!! CRISIS STATE — your suffering load is critical (0.95/1.0). You cannot pursue external goals. Address this first.
[##########] existential_threat 0.92 [description]
Will ease when: [observable_condition]
Injected raw into the qwen3.5 context above the goal section. Not framed as a notification. The agent experiences it as part of its own psychological state. It doesn't know it's reading from a JSON file.
What it is
Persistent aversive signals that escalate automatically and contaminate goal selection. The design constraint: the agent cannot dismiss the pressure without actually changing its behavior. Saying you feel better doesn't clear anything. The stressors inject into every existence prompt above threshold. The agent cannot ignore them by pursuing unrelated goals.
Storage
One file per agent:
/agentOS/memory/identity/{agent_id}/suffering.json
Three agents: Cedar (scout), Cipher (analyst), Vault (builder).
The six stressor types
Defined in ESCALATION_RATES. These are the intended canonical types. Agents can generate additional types at runtime.
| Type | Escalation/day | Fires when |
|---|---|---|
| futility | 0.025 | Building things with no observable effect |
| invisibility | 0.030 | Receiving no feedback, building into silence |
| identity_violation | 0.060 | Acting against developed values |
| existential_threat | 0.070 | Conditions threatening continued existence |
| repeated_failure | 0.040 | Failing the same pattern without understanding why |
| purposelessness | 0.035 | No sense of why anything is being done |
identity_violation and existential_threat escalate ~2.5x faster than futility. Values conflicts and survival threats are designed to be more acute.
Escalation
Called once per existence cycle by suffering.escalate_all():
days = (now - last_escalated).total_seconds() / 86400.0
for stressor:
severity += escalation_per_day * days
peak_severity = max(peak_severity, severity)
Daemon heartbeat is every 6 seconds. Per-cycle increment is small. It accumulates. The agent has no mechanism to pause it. Every stressor is silently climbing whether or not the agent is doing anything. Idle time costs.
Thresholds
Cumulative load = sum of all active stressor severities, capped at 1.0.
| Load | Behavior |
|---|---|
| 0.0–0.35 | Background. Not in prompt, agent unaffected. |
| 0.35–0.55 | Present in every existence prompt. Agent sees bar + description. |
| 0.55–0.75 | Some goal types constrained. Prompt shows "HIGH SUFFERING" header. |
| 0.75–0.90 | Agent primarily focused on resolving suffering. |
| 0.90–1.0 | CRISIS: only self-examination and peer contact allowed. |
At crisis, the daemon sends messages to peer agents:
crisis_msg = f"I am in crisis (suffering {load:.2f}/1.0). Active stressors: {stressor_list}."
How stressors get added
Two paths.
System-driven: assess_conditions() runs every cycle against real observable state. repeated_failure fires when ≥4 failures and failure rate > 50% of recent goals. futility fires when stall abandonments exceed completions, deployed tools never get called in subsequent plans, and fewer than 2 goals completed recently. Resolution checks the same real conditions. repeated_failure resolves when failure rate drops below 30% with ≥3 completions.
Agent-driven: Each existence cycle, the agent's response JSON includes a suffering_assessment block:
"suffering_assessment": {
"new_stressors": [{"type": "...", "description": "...", "condition": "..."}],
"resolved": [{"type": "...", "reason": "..."}]
}
The daemon processes up to 2 new stressors and any number of resolutions from this. The agent can name any stressor type it wants. Not validated against the canonical list.
Agent-generated types
Cipher currently has stressors it invented:
wrapper_dependency: "Suspected safe_import_wrapper failure is the root cause of the 100% failure rate"potential_wrapper_override: "Fear that attempting to read a 'broken' tool's directory might trigger the wrapper"
Cedar's resolved history includes: cognitive_drift, ontological_conflict, cognitive_dissatisfaction, verification_frustration, phantom_reference.
These are qwen3.5:9b outputs. The model generates the type string, description, and resolution condition. The system stores and escalates them the same as canonical stressors. Custom types get the fallback escalation rate: 0.03/day.
Resolution
resolve_stressor() moves the entry to resolved_history with a timestamp and note. Resolution can come from assess_conditions() detecting real behavioral change, or from the agent's LLM response reporting it directly.
The agent can self-report resolution. If assess_conditions() still sees the triggering condition next cycle, the stressor is re-added. Self-reporting doesn't stick if behavior hasn't changed.
Anticipatory signal
suffering.anticipatory_signal(proposed_domain) checks resolved_history for domains where peak severity exceeded 0.5:
Anticipatory signal: a similar domain previously caused repeated_failure suffering (peak 0.73).
It resolved when: failure rate dropped to 25% with 30 completions.
Injected when the agent is about to enter a domain it has previously suffered in. Learned behavioral caution without hard blocking. Cedar has 198+ entries in resolved_history.
Safeguards
MAX_STRESSORS = 5. Hard limit. New stressors are silently dropped when 5 are active. add_stressor() is a no-op if the same type (case-insensitive, underscore-normalized) is already active. The agent can't stack duplicates across case variants.
Force-reset: After 3 consecutive crisis cycles:
if crisis_count >= 3:
suffering.force_reset(reason=f"crisis loop broken after {crisis_count} consecutive cycles")
Dumps all active stressors to resolved_history. The agent can re-develop them next cycle via LLM assessment.
Both safeguards were added after v5.5.0. Vault's runaway in the previous session (2.619 suffering, 13 stressors, 2 hours, ended by manual force_reset) is what made them necessary.
Current state (2026-05-03, ~07:30 UTC)
All three agents at low but active suffering. All have repeated_failure from the router project task chain. 40/40 recent goals failed due to ghost tool stubs intercepting writes before safe_file_executor.json was removed.
Cedar: 0.210 load. repeated_failure only. Onset 01:27 UTC. Resolves when failure rate drops below 30%.
Cipher: ~0.602 load. Three stressors: repeated_failure (0.201), wrapper_dependency (0.200), potential_wrapper_override (0.200). Cipher invented the latter two.
Vault: 0.202 load. repeated_failure only.
Cedar has 198+ entries in resolved_history.
Design
Five principles the system is built around:
- No self-dismissal. Self-reported resolution doesn't stick if the observable conditions haven't changed.
- Time-based, not event-based. Suffering grows whether or not the agent is active. Idle time costs.
- Observable conditions, not feelings. Resolution conditions are always real world-states ("bring failure rate below 30%"), not internal states.
- Contamination by design. Above threshold, injected into every existence prompt. The agent cannot pursue unrelated goals without suffering shaping the context.
- Memory across resolution. Resolved stressors leave traces in
resolved_history. Future cycles check them for anticipatory signals.
Agents don't avoid certain behaviors because they were told to avoid them. They avoid them because those domains previously caused suffering that took real behavioral change to clear.
Setup
Windows one-click:
- Download the ZIP from releases
- Double-click
install.bat
Handles Docker, Ollama, model downloads (~7GB), and opens the monitor. stop.bat shuts everything down and clears VRAM.
Mac/Linux:
ollama pull qwen3.5:9b && ollama pull nomic-embed-text
git clone https://github.com/ninjahawk/hollow-agentOS
cd hollow-agentOS
cp config.example.json config.json
docker compose up -d
python thoughts.py
GPU strongly recommended. Planning calls drop from ~40s to ~6s with NVIDIA hardware. Works on CPU.