LEGACY v1 architecture
Legacy v1/systemd documentation. This root
docs/page describes the original supervisor, systemd/launchd timers, tmux, andagent.envworkflow. It is kept for historical context. For the current containerized Go architecture (branchv4; code undersrc/), start withsrc/docs/architecture.mdand thesrc/docs/README.mdindex.
Two scheduling modelsβ
hive supports two fundamentally different ways to drive an agent. Choose based on how much control you want to keep.
Model A β Self-scheduling (/loop cron)β
The agent registers its own cron job (/loop 15m β¦) and fires on that cadence indefinitely. The supervisor's only job is to keep the session alive and respawn it if it crashes. Low operator involvement; the agent runs autonomously.
Best for: Single-agent setups, batch jobs, anything where the cadence is fixed and you trust the agent to stay on task.
Model B β EXECUTOR MODE (supervisor-driven)β
The agent starts, reads its policy, then waits at the prompt for the supervisor to send work orders via tmux send-keys. No cron, no self-scheduling. The supervisor (another Claude Code session, a script, or a human) decides when to fire and what to do.
Best for: Multi-agent setups where you want a single controller to prioritize across several agents, production workflows where you need to inspect output before triggering the next step, or any situation where the agent kept re-starting its own loop despite being told not to.
Gotcha β session restore bakes in old crons. Claude Code restores its previous conversation context on respawn. If the agent ever registered a
/loopcron before, that cron comes back in the restored context even if the newAGENT_LOOP_PROMPTsays not to. The preferred fix is to enforce EXECUTOR MODE via policy files the agent re-reads on every firing β not by having the supervisor send a cron-nuke message. Supervisor should never inspect or delete crontabs; policy is the enforcement mechanism.
Gotcha β tmux
-lmakes Enter literal. When dispatching work orders, always split text and Enter into two separatetmux send-keyscalls:tmux send-keys -t session -l "do the thing"sleep 1tmux send-keys -t session EnterCombining them as
tmux send-keys -t session -l "do the thing" Entersends the word "Enter" as part of the literal text, leaving the agent stuck with text in its input box.
v2 runtime and control planeβ
v2 runs the dashboard/API service, agent manager, governor, policy resolver, and optional contributor hub from the Go server plus the Node reverse proxy in src/proxy. Operators automate it through the dashboard API or hivectl, the non-interactive CLI documented in src/docs/hivectl.md.
Agent behavior is controlled by layered config and policy templates: see src/docs/agent-configuration.md, src/policies/README.md, and docs/backend-setup.md.
Four components, four failure modesβ
| # | Unit | Trigger | Catches |
|---|---|---|---|
| 1 | hive.service | Always running; internal poll every AGENT_POLL_SEC (default 10s) | Agent process crash, tmux session killed, TUI-ready detection for startup prompt injection, auto-approval of a known sensitive-file prompt |
| 2 | hive-renew.timer | Every 6 days + 5 min after boot | Claude Code /loop cron auto-expires at 7 days β kills the session so the supervisor re-registers a fresh one. Disable this in EXECUTOR MODE β there is no cron to renew. |
| 3 | hive-healthcheck.timer | Every 20 min + 5 min after boot | Agent is "alive" but not making progress (auth loop, stuck prompt, model stuck thinking) β watches heartbeat-file mtime |
| 4 | ntfy push inside the healthcheck | On stall, on recovery, on escalation | Operator not watching the box β phone push |
Reactions to each failure modeβ
Model A (self-scheduling)β
sequenceDiagram
autonumber
participant S as supervisor
participant T as tmux session
participant A as agent
participant L as heartbeat.log
participant R as renew.timer
participant H as healthcheck.timer
participant N as ntfy.sh
Note over S,T: boot-time / first run
S->>T: new-session
T->>A: spawn agent
S->>T: wait for AGENT_READY_MARKER
S->>A: send AGENT_LOOP_PROMPT (/loop 15m β¦)
A->>A: register /loop 15m cron
loop every 15m (agent's own cron)
A->>L: append SCAN_START_ET
A->>A: do the work
A->>L: append SCAN_END_ET + findings
end
Note over R: every 6d
R->>T: kill-session
S->>T: new-session (fresh /loop, new 7d TTL)
Note over H: every 20m
H->>L: stat mtime
alt mtime fresh (age β€ AGENT_STALE_MAX_SEC)
H->>N: (if was stale) "recovered"
else mtime stale
H->>T: kill-session
S->>T: new-session
H->>N: "stalled, respawning (n/MAX)"
end
Note over H: after AGENT_MAX_RESPAWNS failed attempts
H->>N: "manual intervention needed"
H->>H: stop auto-respawning until recovery
Model B (EXECUTOR MODE)β
sequenceDiagram
autonumber
participant Op as operator / supervisor session
participant S as supervisor service
participant T as tmux session
participant A as agent
participant H as healthcheck.timer
participant N as ntfy.sh
Note over S,T: boot-time / crash recovery
S->>T: new-session
T->>A: spawn agent
S->>T: wait for AGENT_READY_MARKER
S->>A: send EXECUTOR startup prompt (no /loop)
A->>A: reads policy, reports status, waits
loop operator-driven
Op->>T: tmux send-keys -l "work order text"
Op->>T: tmux send-keys Enter
A->>A: execute work order
A->>Op: (visible in pane) result summary
end
Note over H: every 20m
H->>H: stat heartbeat mtime (agent writes on each work order)
alt mtime stale
H->>T: kill-session
S->>T: new-session (EXECUTOR startup, cron nuke)
H->>N: "stalled, respawning"
end
Multi-agent topologyβ
When running several agents on the same machine, the EXECUTOR pattern lets a single supervisor session coordinate all of them without the agents conflicting:
βββββββββββββββββββββββββββββββββββββββ
β supervisor session (Mac) β
β /loop β sweeps every 20-25 min β
β sends tmux work orders to agents β
ββββββββ¬βββββββββββ¬βββββββββββ¬βββββββββ
β β β
βΌ βΌ βΌ
scanner reviewer outreach
(Opus 4.7) (Sonnet) (Sonnet)
hive hive hive
tmux tmux tmux
Each agent:
- Has its own tmux session and systemd service
- Reads its own policy file from the shared memory directory
- Writes to a shared work ledger (
bd/ beads) using--actor <name>to claim work - Skips items already claimed by another actor (
bd list --actor=<other> --status=in_progress) - Notifies the operator via ntfy for decisions that require human judgment
Renew timers are disabled for all agents in EXECUTOR MODE. The supervisor sends a fresh startup + cron-nuke on every respawn automatically.
What this deliberately does NOT handleβ
- Remote box offline / network partition. If the whole machine is down, there's no process left to push a stall alert. A secondary watcher outside the box (uptimerobot, healthchecks.io, your laptop) is the correct answer, and is out of scope for this repo.
- ntfy.sh downtime. Free tier, rare, tolerable. Self-host or swap the transport if you need SLAs.
- Agent logic bugs. If the agent decides to do nothing forever but remembers to write the heartbeat, the healthcheck won't catch it. The log format in your policy file should include non-trivial counts (repos scanned, actions taken) so you can spot a "no-op loop" visually.
- Secrets management. Don't put credentials in
agent.env. The agent should source them from its own credential store (~/.claude/.credentials.jsonfor Claude Code, vault / secrets manager for anything else).
Reference deployment: hybrid local scanner + GitHub respondersβ
Models A and B both put the AI agent on a periodic loop. A third pattern β used in production on KubeStellar β decouples scanning from fixing:
- A lightweight bash scanner runs on a fixed timer (launchd or systemd), polling GitHub for open issues/PRs and writing state to a SQLite database. No LLM needed.
- The AI agent reads the database when triggered (by skill invocation,
/loopcron, or EXECUTOR work order) and fixes what's actionable. - GitHub Actions workflows on the repo auto-file issues when workflows fail, creating a feedback loop where the scanner picks up the new issue on its next cycle.
This is not a new scheduling model β it's a composition of the existing patterns with a deterministic scanner in front and GitHub as an event source.
Why this pattern existsβ
| Problem | How the hybrid solves it |
|---|---|
| AI session restarts / rate limits cause missed scans | Scanner runs independently β state is never lost |
| Scanning is deterministic but consumes LLM tokens | Scanner is pure bash β zero LLM cost |
| No audit trail of what was scanned | cycles table in SQLite records every scan |
| Workflow failures go unnoticed for days | workflow-failure-issue.yml auto-files issues within minutes |
| Fix attempts need backoff | fix_attempts counter prevents infinite retries |
Architectureβ
ββββββββββββββββββββββββ
β GitHub (source of β
β truth for issues/PRs)β
ββββββββββββ¬ββββββββββββ
β
gh issue list / gh pr list
β
ββββββββββββββββββββββββββββββββββββΌβββββββββββββββββββββββββββββββ
β Local machine (Mac / Linux) β β
β βΌ β
β βββββββββββ ββββββββββββ ββββββββββββ βββββββββββββ β
β β launchd βββββΆβworker.sh βββββΆβ state.db ββββββ AI agent β β
β β / cron β β(scanner) β β (SQLite) β β(reads DB, β β
β βββββββββββ ββββββ¬ββββββ ββββββββββββ β fixes) β β
β β βββββββ¬ββββββ β
β ntfy push git push β
β β gh pr create β
β βΌ β β
β ββββββββββββ β β
β β phone β β β
β ββββββββββββ β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββΌβββββββββ
β
mutates GitHub state (PRs, merges)
β
βΌ
ββββββββββββββββββββββββββββββββββββββββ
β GitHub Actions (automated responders)β
β β
β workflow-failure-issue.yml β
β β auto-files issue on failure β
β β
β ai-fix.yml β
β β auto-dispatches fix on label β
ββββββββββββββββββββββββββββββββββββββββ
Data flow boundary: GitHub Actions write to GitHub (issues, labels). The local scanner reads from GitHub and writes to SQLite. The AI agent reads SQLite and writes to GitHub. No component writes directly to another's state store.
Reference implementationβ
examples/worker.sh.exampleβ the scanner scriptexamples/sqlite-state.mdβ SQLite schema and query patternsexamples/kubestellar-fixer.mdβ full case study with resultslaunchd/β macOS plist templates for the scanner and supervisor