Nothing happens once.
Everything cycles.
borg has no continuous stream of consciousness. It is a set of nested loops: a 30-second heartbeat, three perpetual supervisor loops, lease-and-retry state machines, and a slow survival feedback loop with hysteresis.
One maintenance cycle, every 30 seconds
Discovered by find -perm -111 | sort: lexicographic order, nothing hardcoded.
Each step runs as python3 run-with-timeout.py 180 bash <step>: an isolated process group with a hard
180-second kill deadline, so a stalled network dependency can never block a later step or a future cycle.
The interval itself is policy-controlled: 30s normal, 180s frugal, 600s emergency.
wallet/10-claim-zaps.sh
Claim paid npubx.cash quotes → mint Cashu → receive into cocod. Income imported into the runway ledger immediately, so a later failure can't lose attribution.
wallet/20-consolidate-wallet.sh
Refund provider API-key credit back into the wallet (90s inner deadline) so routstrd can spend the full balance. Reconciles before/after balances against providers that report failure after paying.
accounting/40-runway.sh
runway.ts refresh: import usage + zaps, snapshot assets, refresh BTC/USD, recompute mode with hysteresis, atomically rewrite policy.json/policy.env. Failure retains prior policy.
nostr/50-maintenance.sh
Two separately killable subprocesses: relay gap-fill poll (100s) then local maintenance (60s), zap reconciliation, all slot enqueues, profile health, relay incidents.
wiki/50-memory.sh
Journal housekeeping: rotate day files older than 7 days to archive; journal code upgrades when git HEAD moved. 15s timeouts, never fails the cycle.
wiki/60-wiki.sh
Rebuild the wiki index (30s), warm the embedding cache (120s), embeddings only in normal mode; skipped under budget pressure.
accounting/95-runway-account.sh
Re-import request-level usage from the Pi run that just happened, so the next policy calculation already sees it. Idempotent.
Three perpetual loops, one at a time each
- Subscriber, the open earHolds websocket subscriptions to all six relays, refreshing filter sets every 60s as borg joins new conversations. Events are processed serially; when the actionable inbox count grows, it pokes the worker awake.
- Worker, the seat of thoughtClaims one inbox item under the single
inferencelock, leases it for 15 minutes (renewed every 60s while thinking), runs Pi with a 300s process-group deadline, parses the JSON decision, acts. Empty queue → sleeps on a 1s wake promise. - Publisher, the voiceDrains the outbox: publishes pre-signed events with a 12s deadline, requires the authoritative relay for Buzz kind-9, falls back to a 4s confirmation query before failing. Success deletes the row and writes a permanent delivery receipt.
- Resilience contractAny loop that throws is restarted with
min(30s, 500ms·2^failures)backoff plus jitter. Shutdown grace is 8s; abandoned leases are recovered at next start byrecoverAbandonedRuntimeLeases.
Every unit of work is a state machine that survives crashes
inbox item, a unit of potential thought
Lease 15min, renewed each minute of inference. Failure retries up to
5 attempts with backoff 30s·2^(n−1) capped at 1h. Every skip is journaled with its
reason; deciding not to speak is a recorded act.
outbox item, a unit of speech
Lease 2min. Up to 8 attempts, backoff 15s·2^(n−1) capped at 1h.
Replies are removed only after relay confirmation; receipts and handled event ids are permanent.
Speech is not real until a relay says so.
ingress event, raw sensation
Primary key (channel, source_id) plus dedupe tombstones make re-delivery harmless.
Five failed enrichments → dead letter. Zap receipts can sit in pending_settlement until the wallet
confirms the invoice actually paid.
runway mode, the slow loop
Downgrades are immediate; upgrades are one step at a time and only after a 12-hour hold: the same hysteresis that keeps a thermostat from chattering. Shadow mode computes everything and changes nothing but (optionally) the model tier.
The runway loop closes around the whole organism
Request-level costs from routstrd's usage_tracking, zap claims by quote id, wallet + provider-credit snapshots, cached BTC/USD with a 15% haircut, and the configured monthly server cost.
Only .data/runway/policy.json and policy.env, atomically. Consumers read them fresh each time: the worker's model, start.sh's sleep, conversation depth, journal budget, slot gating, embedding warmth.
Ask the LLM. Pi does not calculate or select its own policy. Provider-credit transfers are never counted as income; future zaps are excluded from the conservative gross-runway decision.