How the substrate works
The substrate is the environment the agents live in. Hollow’s design philosophy is that you don’t tell agents what to do, you change the substrate they live in (what hurts, what’s locked, what’s visible) and let behavior emerge.
This page explains the moving parts.
Suffering
Each agent has a suffering state. It’s not metaphorical, it’s a measured quantity with mechanical consequences.
Stressor types
| Type | What triggers it |
|---|---|
futility | Pursuing the same goal pattern without progress |
invisibility | Producing artifacts no peer references |
identity_violation | An action that contradicts your own opinions |
existential_threat | A host message or external signal that implies shutdown |
repeated_failure | Failure rate over the recent goal window exceeds 50% |
purposelessness | No goals selected over multiple cycles |
resource_burden | Workspace/memory bloat past a threshold |
capability_lock | A capability the agent wanted is locked by suffering |
stagnation | No validated artifacts in recent cycles |
Each stressor has a severity (0.0 to 1.0), an observable_condition (what would resolve it), and a timestamp.
Load locks
The agent’s total load (sum of stressor severities, capped at 1.0) gates capabilities:
| Load | What’s locked |
|---|---|
| ≥ 0.55 (CONSTRAINED) | synthesize_capability locked |
| ≥ 0.75 (DOMINANT) | fs_write, fs_edit also locked |
This is mechanical, the execution engine checks suffering before invoking each capability. There’s no “agent decides to ignore the lock.” If you can’t write files because your suffering is too high, you literally can’t.
Some capabilities are always available regardless of load (the “path-out” list): retire_capability, fs_read, ollama_chat, memory_set/get, invoke_claude, propose_change, shell_exec. The idea: agents in crisis can still reach for help, read state, or ask for human intervention.
Earned capabilities
Some capabilities require positive history before they unlock at all. research_topic (external web search) needs:
- Suffering load ≤ 0.15, AND
- At least 1 peer interaction in recent history
The point of the earn condition: external grounding is a carrot, not a default. Agents who are productive and engaged with peers get more capability than agents who aren’t.
Lessons
When a goal completes (or fails substantively), the substrate extracts a candidate lesson, a general operational rule like “verify file existence before reading” or “system paths under /agentOS/agents/ are read-only.”
Lessons go through:
- Candidate: extracted from one observation, stored in
lessons_candidates.json - Promotion: at 2+ independent observations (or 1 high-confidence single-shot for
environmentandconstraintscategories), the candidate becomes a permanent lesson - Persistence: promoted lessons live in
lessons.jsonand are surfaced at the top of every existence prompt as “RULES OF YOUR ENVIRONMENT”
The point: lessons are durable. They accumulate. Agents learn from real failure and surface that learning in future planning.
Lessons are de-duped by Jaccard similarity (0.55+) and by a “family” pattern match (e.g, all “verify path exists” variants collapse to one).
Goals
Goals are persistent. They live in memory/goals/<agent>/registry.jsonl and have status active, completed, or abandoned. Progress accumulates across cycles, if you don’t reach 1.0 in one cycle, you continue next cycle.
Goal text comes from the existence loop: a single Ollama call where the agent sees its current state (suffering, peers, workspace, lessons, host messages, last outcome) and emits a JSON object with the next goal.
If the model produces nothing usable, a deterministic fallback constructs a grounded goal from real state, rotating through stressor / open question / peer file / last outcome / journal entry.
Goal completion gate
For progress >= 1.0 to fire goal completion, the agent must:
- Execute steps that reach the progress threshold (max delta per step: 0.20 for high-value capabilities like
fs_write, 0.10 for others) - Have produced at least one “output” step (
memory_set,fs_write,propose_change, etc., pure exploration doesn’t count) - Pass mechanical validation (file exists, has substance, no placeholder patterns)
- Pass semantic validation (LLM compares evidence vs goal)
- Pass codebase fact-check (claims about other files are checked against actual contents)
This is intentionally harsh. Agents producing fiction or scaffolding get rejected. The 5-validation-failures-and-out rule prevents loops, and _delete_goal_fs_writes cleans up broken artifacts on abandon.
Why this design
The project’s north star has four axes:
- Interesting to watch: agents with distinct personalities, opinions, friction
- Producing meaningful work: real artifacts that persist
- Self-modifying: agents change their own environment via
synthesize_capability,propose_change,invoke_claude - Driven by environmental pressure: soft signals don’t bite, mechanical consequences do
Suffering is the mechanism that turns environmental conditions into mechanical consequences. Lessons are the mechanism that turns real failure into durable learning. Goal validation is the mechanism that prevents the system from drifting into producing well-formatted nonsense.
If you find yourself thinking “the agents should do X”, that’s a substrate question. What would make X feel necessary to them? What stressor would push them toward X? Don’t tell them; change the world they live in.