Two things the entity window redesign surfaced but deliberately left alone. **blast_radius answered the wrong question.** It walked source→target for every relationship type, but which end of an edge is the dependent differs per type: "machine hosts container" means the target breaks, while "service depends-on service" and "ingress routes-to service" mean the SOURCE breaks. Walking everything forwards was right for hosts/provides and backwards for everything else — and swept in 2,800+ documents/involves/targets edges of pure bookkeeping, so the result contained tasks and executions that cannot break. Direction is now declared per relationship type in seeds/ontology.yaml (blast_direction: forward | backward | none), the same shape as the entity types' monitoring: declaration, and defaults to none so an undeclared edge contributes nothing rather than a confidently wrong answer. It also needed a modelling fix: `routes-to` names an ingress's BACKEND, so nothing recorded that all 21 public hostnames are terminated by caddy. A `served-by` edge type now says so. pool:ludo-lvm 2 -> 23 (every container storing on it, then their services) lxc:caddy 4 -> 22 (service:caddy, then all 21 ingress routes) service:authentik 7 (what authenticates via it) **Every ping check was reporting down.** Not a host:strong false positive: all seven, including ws:mac-mini — the Docker host itself. The scheduler runs in Docker on macOS, whose VM does not route ICMP to the LAN; loopback pings succeed and every LAN ping fails. Under health aggregation each broken probe dragged its entity to down. The question the check exists to answer is "is it reachable", and ICMP is only one way to ask it. checkPing now falls back to a TCP connect before concluding anything, which restores an honest verdict for the four hosts that are genuinely up while leaving the genuinely unreachable ones down. TestBlastRadiusTerminatesOnCycles asserted the old direction (caddy=1, authentik=2 — the cycle walked the wrong way); it now asserts the corrected depths, and its exact-node-count check is relaxed because walking the right way also surfaces the seed's own real dependents, which are correct answers. Co-Authored-By: Claude <noreply@anthropic.com>
Oikos
Agentic homelab operating system written in Go. Single binary (cmd/oikos),
Docker-deployed on mac-mini, with a standalone Nomos MCP agent gateway
(cmd/nomos). Manages the hubris Proxmox homelab autonomously — observes
state, classifies actions against policy, executes approved procedures over SSH,
learns from outcomes, and escalates when uncertain.
For agents running on enrolled clients: start with AGENTS.md. For client machines: see CLIENTS.md. For developers: see CONTRIBUTING.md.
Quick start
# Dev stack (postgres + api + scheduler + notifier). The api/nomos
# services need a shared token — every route requires a real bearer
# credential, there's no dev-open bypass.
OIKOS_MCP_BEARER_TOKEN=dev-token docker compose --profile dev up -d
# Full stack (adds Nomos agent gateway)
OIKOS_MCP_BEARER_TOKEN=dev-token docker compose --profile full up -d
# Build standalone binary
go build -o bin/oikos -tags timetzdata ./cmd/oikos
# Run all roles in one process (dev mode)
OIKOS_DATABASE_URL="postgres://oikos:oikos_dev@localhost:5432/oikos?sslmode=disable" \
OIKOS_API_TOKEN=dev-token \
go run ./cmd/oikos all
# Control-room SPA (separate from the Go binary — see web/)
cd web && OIKOS_API_TOKEN=dev-token npm run dev # http://localhost:5173
Architecture
┌──────────────────────────────────┐
│ mac-mini (Docker) │
│ │
Workstation ─── │ nomos (8092) ──MCP── api (8090) │
(mesh) │ MCP gateway REST + MCP │
│ │
│ scheduler ── notifier ── postgres │
│ (observe) (Matrix) (Timescale)│
└──────────────────────────────────┘
| Component | Port | Role |
|---|---|---|
oikos api |
8090 | REST API + MCP server (tool list in AGENTS.md §3) |
oikos scheduler |
— | Probe runner, signal lifecycle, metrics |
oikos notifier |
— | Approval tokens, Matrix alerts |
nomos serve |
8092 | MCP client gateway, query routing |
Phases
| Phase | Status | Description |
|---|---|---|
| 1 — Ontology + DB | ✅ | TimescaleDB, migrations, seeds, blast_radius |
| 2 — API | ✅ | OpenAPI-first REST + MCP, auth, SSE, audit |
| 3 — Control loop | ✅ | Scheduler, actuator, learning, classifier, notifier |
| 4 — Nomos agent | ✅ | Standalone MCP client gateway, agent activity |
| 5 — Secrets | ✅ | Infisical backend + SOPS fallback, rotation runbooks |
| 6 — Deploy | ✅ | CI pipeline, cutover checklist, watchdog, rollback |
Full plan: plans/done/2026-07-06-consolidate-oikos-control-plane-onto-mac-mini.md.
Operations
API endpoints
curl -H "Authorization: Bearer $OIKOS_API_TOKEN" \
http://localhost:8090/api/v1/entities?type=service # fleet
curl -H "Authorization: Bearer $OIKOS_API_TOKEN" \
http://localhost:8090/api/v1/health # fleet health
curl -H "Authorization: Bearer $OIKOS_API_TOKEN" \
http://localhost:8090/api/v1/agent-activity # agent log
Nomos queries
# Structured tool call
curl -X POST localhost:8092/query -H "Content-Type: application/json" \
-d '{"tool":"get_blast_radius","args":{"entity_id":"service:authentik"}}'
# Natural language
curl -X POST localhost:8092/query -H "Content-Type: application/json" \
-d '{"query":"what depends on authentik?"}'
CLI
oikos migrate # apply DB migrations
oikos seed # ingest ontology/inventory/policy seeds
oikos export # export DB state to YAML
oikos api # serve REST + MCP
oikos scheduler # run observe loop
oikos notifier # run notification loop
oikos all # all roles in one process
oikos secret list # enumerate SOPS secrets
oikos secret migrate # SOPS → Infisical
Web UI
web/ is a standalone Svelte 5 SPA — not embedded in the oikos binary, not
part of docker-compose.yml. It talks to api/nomos over HTTP with a
bearer token entered on first launch (see web/src/pages/Config.svelte).
Build with make ui, deploy with make deploy-ui (Caddy serves the static
output). A native desktop wrapper exists at cmd/desktop/ — see
plans/done/2026-07-12-wails-desktop-app.md.
Repo layout
cmd/oikos/ Go entry point — single binary
cmd/nomos/ Nomos MCP client gateway
cmd/webhook/ Gitea deploy-webhook receiver (push-to-deploy on mac-mini)
cmd/desktop/ Wails desktop wrapper around the SPA
internal/ Go packages (actuator, checkdefaults, config, db, domain,
httpapi, knowledge, learning, mcp, notifier, observability,
ontology, policy, safego, scheduler, secrets)
web/ Control-room SPA (Svelte 5) — standalone, not embedded
api/openapi.yaml API contract (OpenAPI 3.1)
migrations/ Forward-only SQL migrations (TimescaleDB)
seeds/ Bootstrap YAML (ontology, inventory, policy, knowledge)
compose/ Dockerfiles + Caddy config
scripts/ Deploy, watchdog, verification, rollback
checks/ Host health-check scripts run over SSH by the scheduler
tools/ Client auto-setup scripts (checks)
ssh/ Deploy keys + authorized_keys management
vps/ Caddy/TURN config templates for the netbird VPS
nomos/ Nomos config, persona, skills
.agents/ Agent instruction files, shared conventions, skills
archive/ Historical reference (legacy wiki, plans, SOPS backups)
plans/ Design documents (active + done)
docs/adr/ Architecture decision records
docs/operations/ Runbooks (rollback, etc.)
For agents
See AGENTS.md for the full orientation. Quick reference:
- Source of truth: DB (runtime) then seeds (bootstrap). Old wiki is
archived at
archive/knowledge/— use MCPsearch_knowledgeinstead. - Mutations: classify against policy, request approval for
destructive/config_mutation - Secrets: Infisical (primary) or SOPS (fallback) — never hardcode
Related
- OIKOS.md — operating model, OODA loop, ontology
- CLIENTS.md — client onboarding guide
- CONTRIBUTING.md — developer guide
- plans/ — design documents and cutover checklist
- docs/adr/ — architecture decision records