Architecture¶
Hive is a local-first autonomous agent OS. Agents are persistent entities driven by a daemon heartbeat loop, with tools, memory, suffering, and multi-model support.
Directory Structure¶
src/hive/
├── cli/ # Typer CLI -- commands map 1:1 to user actions
├── daemon/ # Background service
│ ├── loop.py # HiveDaemon -- heartbeat loop driving all agents
│ ├── hooks.py # HookRegistry -- lifecycle event callbacks
│ ├── lifecycle.py # Agent spawning and killing
│ ├── diagnostics.py# Health checks and status reporting
│ └── setup.py # Hive directory initialization
├── agents/ # Agent state and behavior
│ ├── profile.py # AgentProfile -- YAML-driven configuration
│ ├── state.py # AgentState, AgentStatus enum
│ ├── existence.py # ExistenceLoop -- autonomous goal generation
│ ├── goal_strategy.py # GoalStrategy protocol, GoalContext, Goal
│ ├── suffering.py # SufferingState, StressorRegistry, stressor types
│ ├── identity.py # IdentityManager -- narrative and opinions
│ ├── delegation.py # DelegationEngine -- inter-agent task routing
│ ├── specialization.py # SpecializationTracker -- task expertise
│ └── swarm.py # SwarmLearning -- cross-agent pattern discovery
├── runtime/ # Agent execution framework
│ ├── agent.py # Agent -- ReAct loop with tools
│ ├── persona.py # Persona -- dynamic personality that evolves
│ ├── types.py # Message, ToolCall, ToolResult, GenerateResult
│ ├── instructions.py # Instructions base class
│ ├── workflow.py # Workflow -- multi-step task execution
│ └── plugin_loader.py # Hot-load Toolkit subclasses from plugins/
├── models/ # LLM provider implementations
│ ├── base.py # BaseProvider ABC
│ ├── factory.py # create_runtime_provider() -- routes model names
│ ├── registry.py # ModelRegistry -- models.yaml loader
│ ├── anthropic.py # Anthropic provider (Claude)
│ ├── openai.py # OpenAI provider
│ ├── fireworks.py # Fireworks provider
│ ├── groq.py # Groq provider
│ ├── ollama.py # Ollama provider (local)
│ └── lmstudio.py # LM Studio provider (local)
├── interactions/ # Multi-agent protocols
│ ├── a2a.py # A2AStore, A2AMessage, A2AMessageType
│ ├── a2a_patterns.py # A2APattern ABC + 5 built-in patterns
│ ├── registry.py # PatternRegistry, InteractionPatternRegistry
│ ├── base.py # Participant protocol, InteractionPattern ABC
│ ├── exchange.py # ExchangeRunner -- lightweight orchestrator
│ ├── runner.py # ScenarioRunner -- end-to-end scenario execution
│ ├── presets.py # Pre-built ExchangeConfig factories
│ └── patterns/ # round_table, pairs, freeform
├── memory/ # Persistence layer
│ ├── store.py # HiveStore -- SQLite via aiosqlite
│ ├── events.py # EventLog -- JSONL append-only event stream
│ ├── semantic.py # SemanticMemory -- TF-IDF similarity search
│ └── goals.py # GoalEngine -- hierarchy and priority
├── tools/ # 15 toolkit modules
│ ├── base.py # Toolkit, Tool, @tool() decorator
│ └── */toolkit.py # a2a, comms, delegation, file, git, mcp, memory,
│ # notepad, schedule, shell, sub_agents, web, world
├── world/ # Simulated economy
│ ├── state.py # WorldState -- money, jobs, skills
│ ├── event_engine.py # Random life events
│ └── stats.py # StatsManager
├── logging/ # Structured run logs
│ ├── models.py # CycleLog, GoalLog, SufferingLog, DecisionLog
│ └── writer.py # LogWriter -- writes to logs directory
├── mcp/ # MCP client and server
├── checkpoint.py # Save/restore agent state snapshots
├── api.py # Hive facade class -- programmatic Python API
└── config.py # HiveConfig -- YAML + env var loading
Core Classes¶
| Class | File | Purpose | Key Methods |
|---|---|---|---|
Agent |
runtime/agent.py |
ReAct loop agent with tools | run(), run_once(), run_once_structured() |
Persona |
runtime/persona.py |
Dynamic personality with suffering effects | build_system_prompt(), apply_suffering_effects(), snapshot() |
HiveDaemon |
daemon/loop.py |
Heartbeat loop driving all agents | start(), hooks property |
HookRegistry |
daemon/hooks.py |
Event bus for lifecycle callbacks | on(), off(), emit() |
ExistenceLoop |
agents/existence.py |
Autonomous goal generation | generate_goal(), generate_goal_from_context() |
GoalStrategy |
agents/goal_strategy.py |
Protocol for pluggable goal gen | generate_goal(context) |
SufferingState |
agents/suffering.py |
Per-agent suffering tracking | add_stressor(), escalate_all(), resolve() |
StressorRegistry |
agents/suffering.py |
Extensible stressor types | register(), get(), all_types() |
AgentProfile |
agents/profile.py |
YAML-driven agent configuration | build_system_prompt() |
Toolkit |
tools/base.py |
Base class for tool groups | get_tools(), bind() |
Tool |
tools/base.py |
Single callable tool | call(), to_schema() |
BaseProvider |
models/base.py |
LLM provider ABC | generate(), generate_with_metadata(), generate_structured() |
PatternRegistry |
interactions/registry.py |
A2A pattern registry | register(), get(), list_patterns() |
A2APattern |
interactions/a2a_patterns.py |
Collaboration pattern ABC | execute() |
A2AStore |
interactions/a2a.py |
Agent-to-agent message store | send(), get_inbox(), get_pending_requests() |
HiveStore |
memory/store.py |
SQLite persistence | save_goal(), complete_goal(), list_agents() |
EventLog |
memory/events.py |
JSONL event stream | append() |
Performance and Persistence Notes¶
- Concurrent tool execution. When a model turn emits several tool calls, the
Agentruns them concurrently (asyncio.gather) rather than one at a time, so a turn's wall-time tracks the slowest tool instead of their sum. Each call is isolated -- a raised exception or unknown tool name becomes an error result for that call without affecting its siblings -- and results are appended to the conversation in the original call order so transcripts stay deterministic. - Concurrent agent cycles. Each heartbeat runs alive agents' cycles concurrently
with bounded concurrency (
max_concurrent_agents, a semaphore). Every cycle is isolated -- its own timeout and error handling -- so one slow, timed-out, or failing agent never blocks or breaks the others that heartbeat. Setmax_concurrent_agents: 1for fully sequential behavior. Each agent's provider and profile are cached across cycles (rebuilt only when the model changes or the profile YAML's mtime changes), alongside the existing per-agent memory and persona caches. - Tool discovery is cached. A
Toolkitdiscovers its@toolmethods once (atbind()or firstget_tools()) and reuses the result;rebind()swaps the agent id without rebuilding the cache (a copy resets the cache so a clone binds to itself). - Indexed, versioned SQLite.
HiveStoreindexes hot lookup columns (agent_id,status,fire_at,parent_goal_id, ...) and tracks its schema with SQLite'sPRAGMA user_version. Schema changes are ordered migration steps applied in a single transaction oninitialize(), so an older database upgrades in place without data loss. - WAL journaling + cascades. The store runs in WAL mode (set once on
initialize(), persistent in the DB header) with a 5s busy timeout, so readers and a writer don't lock each other out across concurrent agent cycles or other processes (the MCP server, the CLI). Child tables (sessions,goals,tasks, ...) declareON DELETE CASCADEon their foreign key toagents, soHiveStore.delete_agent()removes an agent and all of its rows together. FK enforcement is opt-in per operation, so writing child rows for a standalone agent that was never persisted toagentsstill works. - Crash-durable event log. The JSONL session log is append-only by construction.
Setting
event_log_fsync: true(envHIVE_EVENT_LOG_FSYNC) flushes andfsync()s every append so an acknowledged event survives a power/OS crash; it defaults to off because that is onefsyncper event on the hot write path. Readers tolerate a torn last line from an interrupted append. - Deterministic mode. Setting
seed(envHIVE_SEED) gives the stochastic world layer reproducible RNG streams: the daemon injects a seededrandom.RandomintoEventEngine(event rolls, luck, follow-ups) and a separately-derived one intoWorldState(gambling), so one subsystem's draws don't perturb the other. Each run writes amanifest.json(logs/runs/<run-id>/) with the hive version, seed, model config, and spawned agents. The seed governs the world RNG only -- LLM outputs are not deterministic -- so the manifest, not bit-for-bit replay, is what makes a run reproducible.
Extension Points¶
| What | How | File | Example |
|---|---|---|---|
| Custom tool | Subclass Toolkit, decorate methods with @tool() |
tools/base.py |
See EXTENDING.md |
| Custom model provider | Subclass BaseProvider |
models/base.py |
See EXTENDING.md |
| Custom stressor | StressorRegistry.default().register(...) |
agents/suffering.py |
See EXTENDING.md |
| Custom A2A pattern | Subclass A2APattern, register via PatternRegistry |
interactions/registry.py |
See EXTENDING.md |
| Custom world content | EventRegistry/JobRegistry .default().register(...) |
world/registry.py |
See EXTENDING.md |
| Custom goal strategy | Implement GoalStrategy protocol |
agents/goal_strategy.py |
See EXTENDING.md |
| Daemon hooks | daemon.hooks.on("event", callback) |
daemon/hooks.py |
See EXTENDING.md |
| Custom agent profile | YAML file in profiles/ |
agents/profile.py |
See EXTENDING.md |
| Plugin toolkit | Drop Toolkit subclass in .hive/plugins/ |
runtime/plugin_loader.py |
See EXTENDING.md |
Data Flow -- Daemon Cycle¶
Each heartbeat (default 10s):
1. Hot-load plugins (every 10 cycles)
2. For each alive agent (run concurrently, bounded by max_concurrent_agents;
each cycle isolated so one slow/failing agent never blocks the others):
a. emit("cycle_start")
b. Escalate all stressors
c. Load profile, identity, persona, memory
d. Apply suffering → persona behavioral effects
e. If active goal:
- Pursue goal via Agent ReAct loop
- Assess conditions (fire/resolve stressors)
- On success: emit("goal_completed"), checkpoint, update narrative
- On failure: emit("goal_abandoned"), update narrative, record stressor
f. If idle:
- Check scheduled goals
- Generate a goal (custom GoalStrategy or ExistenceLoop). The prompt
includes the agent's stats (health/energy/happiness/reputation) so a
drained or unwell agent steers toward recovery (D1)
- emit("goal_generated")
g. Log suffering state
h. emit("suffering_changed")
i. emit("cycle_end")
3. Auto-kill expired sub-agents
4. Every 5 cycles: swarm learning
5. If economy: process payday + life events. A life-event Choice may declare a
`stressor` it causes or `resolves_stressor` it relieves -- the daemon feeds
these into the agent's suffering, and records the event in its narrative (D1)
6. Sleep heartbeat seconds
Feedback loops (D1). The world, suffering, goals, and identity influence each other: life events add/resolve stressors and append to the narrative; agent stats feed goal generation; and both goal and event outcomes shape the agent's story.
Configuration¶
All config lives in .hive/config.yaml and env vars.
| Section | Key | Type | Default | Description |
|---|---|---|---|---|
suffering |
threshold_prominent |
float |
0.35 |
Show suffering in prompts |
suffering |
threshold_constrained |
float |
0.55 |
Suffering limits options |
suffering |
threshold_dominant |
float |
0.75 |
Suffering demands attention |
suffering |
threshold_crisis |
float |
0.90 |
Crisis mode threshold |
suffering |
max_stressors |
int |
5 |
Max concurrent stressors |
suffering |
initial_severity |
float |
0.20 |
Starting severity for new stressors |
suffering |
crisis_reset_after |
int |
3 |
Cycles in crisis before force reset |
suffering |
escalation_rates |
dict[str,float] |
Per-type | Daily escalation rate per stressor |
economy |
enabled |
bool |
true |
Enable economy simulation |
economy |
starting_balance |
float |
100.0 |
Initial agent balance |
economy |
skill_course_cost |
float |
80.0 |
Cost to learn a skill |
economy |
learnable_skills |
list[str] |
6 skills | Available skills to learn |
daemon |
heartbeat |
int |
10 |
Seconds between cycles |
daemon |
max_retries |
int |
2 |
Retries on agent failure |
daemon |
cycle_timeout |
int |
300 |
Per-agent cycle timeout in seconds (0 = none) |
daemon |
max_concurrent_agents |
int |
8 |
Max agent cycles run concurrently per heartbeat |
daemon |
tool_timeout |
float |
60.0 |
Per-tool wall-clock limit in seconds (0 = none); a hung tool becomes a tool-error instead of stalling the cycle |
model |
default_model |
str |
claude-haiku-4-5 |
Default LLM model |
model |
planning_model |
str |
claude-sonnet-4-6 |
Model for planning tasks |
model |
max_tokens |
int |
4096 |
Max generation tokens |
model |
temperature |
float |
0.0 |
Generation temperature |
tools |
shell_pass_env |
bool |
false |
Pass secrets (API keys, tokens) to agent shell commands; scrubbed by default |
tools |
shell_allow_dev_commands |
bool |
true |
Allow python/git/curl etc. in the restricted shell (these can escape the workspace jail) |
tools |
file_max_read_bytes |
int |
10000000 |
Refuse file reads larger than this |
tools |
file_max_write_bytes |
int |
10000000 |
Refuse file writes larger than this |
plugins |
enabled |
bool |
true |
Load plugin toolkits from .hive/plugins/ |
plugins |
allowlist |
list[str] |
[] |
Only load these plugin filenames/stems; empty = all |
retention |
enabled |
bool |
false |
Periodically delete terminal rows (resolved approvals, fired alarms, delivered nudges, finished sessions/delegations) and deny dead agents' pending approvals |
retention |
days |
int |
30 |
Only rows older than this are deleted |
retention |
interval_cycles |
int |
100 |
Run the janitor every N heartbeats |
server |
api_key |
str |
"" |
Require X-Hive-Key on REST data routes; empty = no auth (also HIVE_API_KEY) |
server |
cors_origins |
list[str] |
[] |
Allowed CORS origins; empty = no CORS middleware |
server |
session_ttl_hours |
int |
0 |
Expire running sessions idle longer than this (via the janitor); 0 = never |
Env var overrides: HIVE_HEARTBEAT, HIVE_DEFAULT_MODEL, HIVE_STARTING_BALANCE, etc.