Files
oikos/plans/2026-07-07-migrate-bin-homelab-to-go.md

9.0 KiB

Plan: Complete MCP tool surface — Hermes as the primary operator interface

Status: Planned (2026-07-07, rev 2) — supersedes rev 1 (CLI port was the wrong paradigm)

Goal

The operator (dtoro) interacts with the homelab through Hermes, the AI agent. Hermes talks to oikos api via MCP protocol at :8090. The command-line homelab CLI is an implementation detail — the agent is the interface.

This plan completes the MCP tool surface so Hermes can observe, orient, decide, and act on the full homelab without the operator touching a shell. Once complete, bin/homelab (Python) and bin/oikos (stale binary) are deleted.

Architecture

┌──────────────────────────────────────────────────────────┐
│                    dtoro (operator)                       │
│  Interface: Hermes (natural language via chat/matrix)    │
│  Also: Oikos Console (web UI, future)                    │
└──────────────────────┬───────────────────────────────────┘
                       │ delegates tasks
                       ▼
┌──────────────────────────────────────────────────────────┐
│              Hermes (AI agent, Docker :8092)              │
│                                                          │
│  Skills: health triage, signal response, execution       │
│  tracking, pattern learning, homelab ops, knowledge      │
│                                                          │
│  Protocol: MCP streamable HTTP → oikos api :8090         │
│  Authority: auto-act on reversible_low only; escalates   │
│  config_mutation/destructive to operator via Matrix      │
└──────────────────────┬───────────────────────────────────┘
                       │ MCP tools
                       ▼
┌──────────────────────────────────────────────────────────┐
│              oikos api (Go, Docker :8090)                 │
│                                                          │
│  REST API + MCP server + SSE event stream                │
│  Same DB, auth middleware, audit log, policy engine      │
│                                                          │
│  Actuator (separate container, restricted SSH key)       │
│  Notifier (Matrix alerts)                                │
│  Scheduler (OODA loop: probes, signals, patterns)        │
└──────────────────────────────────────────────────────────┘

Current MCP tool surface (15 tools)

Observe — complete

Tool What it does
get_entity Get entity by slug or UUID
list_entities List entities by type, state, search
get_relations Get relationships for an entity
get_blast_radius Entities affected if this entity goes down
get_health_summary Current fleet health summary
get_signal_history Open and recent signals
get_trend Metric trends for an entity
get_event_timeline Recent events
query_metrics Time-series metric queries
search_knowledge Full-text search across docs/runbooks

Orient — complete

Tool What it does
get_patterns Learned action patterns
get_skills Available automation skills
get_audit_trail Audit log queries

Act — ⚠️ exists, limited scope

Tool What it does
request_execution Hermes-only mutation path. Takes target (entity slug) + action (verb). Policy-gated.

Self — complete

Tool What it does
get_agent_activity Agent self-inspection

What's missing — MCP tools to add

Phase 1 — Operational visibility (P0, this week)

Tools Hermes needs to answer "what's happening right now?"

Tool Input Output Implementation
tail_log service_slug, lines Last N log lines via journalctl SSH to host, journalctl -u <service> -n <lines>
get_service_status service_slug systemctl is-active / is-enabled + uptime SSH to host, systemctl show
ping_service service_slug HTTP/TCP reachability check HTTP GET health endpoint from oikos scheduler probe data
list_lxcs none All LXCs with ID, host, IP, state DB query from entity_status table
get_lxc_state lxc_slug RAM, CPU, disk, uptime SSH to Proxmox host, pct status <vmid>

Phase 2 — Execution expansion (P1, next 2 weeks)

Expand request_execution to handle the full operational surface. Currently it writes a generic execution record. It needs to understand action types and route them:

Action type What Hermes says What actuator does
restart "restart lxc:caddy" systemctl restart <service> on target host
logs "show me caddy logs" Already handled by tail_log tool
pct_exec "run apt update on LXC 121" pct exec <vmid> -- <cmd> on Proxmox host
apt_upgrade "upgrade packages on lxc:caddy" apt update && apt upgrade -y in detached screen
apt_audit "audit packages on all LXCs" dpkg -l + upgradable count per host
systemctl "enable service on lxc:foo" systemctl <verb> <service> (gated: enable = config_mutation)

Each action type needs:

  1. A clear input schema (what params Hermes must provide)
  2. Policy classification (risk class, blast radius)
  3. Verification step (how to confirm it worked)
  4. Ledger entry (what happened, when, by whom)

Phase 3 — Agent-to-operator escalation (P1, next 2 weeks)

When Hermes hits a config_mutation or destructive action, it escalates to the operator. This path needs to be solid:

Component Current state Target
Matrix alert ⚠️ Notifier polls DB, but Hermes doesn't trigger it Hermes calls request_execution → policy gate rejects → notifier sends Matrix alert with approval token
Approval flow ⚠️ oikos homelab approval exists in Go but agent can't use it Operator reacts / on Matrix → webhook → approval token consumed → actuator proceeds
Execution tracking request_execution writes to executions table Add get_execution_status MCP tool so Hermes can poll for results

Phase 4 — Cleanup (P2, after all tools work)

Action Notes
Delete bin/homelab Python CLI. Disconnected from DB, imports deleted modules. Agent handles everything.
Delete bin/oikos Stale pre-built arm64 binary. Docker image is the canonical build.
Remove oikos homelab Go subcommand The homelab CLI role in the Go binary is a dead end. Remove it or keep as debug-only.
Update AGENTS.md Document the complete MCP tool surface. Remove CLI references.

Implementation approach

Each new MCP tool is registered in internal/mcp/server.go following the existing pattern:

register(&mcp.Tool{Name: "tail_log", Description: "...", InputSchema: ...},
    func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) {
        // ...
    })

SSH-based tools (tail_log, get_service_status, list_lxcs, get_lxc_state) reuse the mesh-address resolution already working for request_execution: entity slug → DB lookup → inventory attributes → mesh IP → ssh user@ip.

The actuator container holds the SSH key. MCP tools that need SSH should route through the actuator's execution queue rather than holding their own SSH key, maintaining the security boundary: Hermes → MCP → API → execution queue → actuator → SSH.

Verification

  • Hermes can answer "show me caddy logs" → tail_log returns journal lines
  • Hermes can answer "what's the fleet health?" → get_health_summary + list_lxcs
  • Hermes can answer "restart caddy" → request_execution(action="restart", target="lxc:caddy") → actuator SSHs → caddy restarts → Hermes confirms
  • Hermes can answer "upgrade packages on dns LXC" → request_execution(action="apt_upgrade", target="lxc:dns") → gated → actuator runs in screen → Hermes reports result
  • bin/ directory is empty or contains only Docker-related scripts

Changelog

  • 2026-07-07 rev 2 — flipped paradigm. Operator interface is Hermes via MCP, not CLI. Replaced 4-phase CLI port plan with 4-phase MCP completion plan.
  • 2026-07-07 rev 1 — cataloged 27 Python CLI subcommands vs 4 Go subcommands.