Skip to content

Repository files navigation

Sekhmet

A self-hosted, agentic AI platform that autonomously monitors and heals a fleet of Linux servers.

See Claude.md for the full build specification and ARCHITECTURE.md for the design rationale — the safety model (whitelist + policy gate) is the part worth reading first.

Screenshots

The fleet dashboard (http://localhost:8080) — nodes, pending approvals, and the incident timeline, all live:

Dashboard

Click any incident to expand its full record — the pre-fix metrics, the LLM's actual diagnosis and reasoning, the action taken, and the outcome, with a direct link to the full LangFuse trace:

Incident detail

Status: all phases implemented

  • Phase 0 — monorepo scaffold, shared proto (compiles for Go + Python), Docker Compose stack, mTLS dev CA.
  • Phase 1 — Node Agent collectors (CPU, memory, FDs, connections, processes, sysctl) streaming to the Brain over gRPC; persisted to InfluxDB.
  • Phase 2 — static-threshold detection, the 6 whitelisted remediation executors with adversarial allowlist tests, the deterministic policy gate, full Postgres audit logging, CLI-based human approval.
  • Phase 3 — the full LangGraph agentic core (Coordinator / Diagnostician / Remediation), Ollama-backed diagnosis and planning with bounded-retry and deterministic fallback, restart-survivable checkpointing for the human-approval pause.
  • Phase 4 — Qdrant recall (resolved cases + runbooks), Neo4j service-dependency blast-radius (escalates risk for wide-blast-radius incidents), MCP tool servers exposing metrics/graph/action-schema to the LLM.
  • Phase 5 — minimal read-only dashboard (fleet, pending approvals, incident timeline).

Recent hardening

  • Node agent reconnects. A dropped gRPC stream (Brain restart, network blip) no longer kills the agent — it redials with exponential backoff (1s→30s capped) indefinitely, and metrics keep accumulating in the buffered channel while disconnected.
  • RaiseUlimitNofile's service→PID resolution is real, not a stub — queries systemctl show -p MainPID and fails loudly (never a fabricated PID) if systemd isn't present or the unit isn't running.
  • LangFuse tracing is actually wired up and verified, not just scaffolded — see Observability below.
  • Automated integration test suite (brain/tests/integration/test_end_to_end.py) drives the full pipeline for all the canonical incident shapes against the real Postgres/Qdrant/Neo4j/InfluxDB stack, asserting detect→diagnose→ plan→gate→execute→verify and a complete audit trail — this is what caught a real bug (see below).
  • Found and fixed while building this: a plan whose params failed whitelist validation (e.g. no offending PID was resolvable) used to crash the whole LangGraph run with an uncaught exception instead of being recorded as a graceful rejection. Fixed in node_execute; regression- tested in the integration suite.

Quickstart

# 1. Generate mTLS certs (once)
./deploy/certs/gen-certs.sh target-1 target-2

# 2. Bring up backing services (Postgres, Qdrant, Neo4j, InfluxDB, Ollama, LangFuse, 2 target containers)
cd deploy/compose && docker compose up -d && cd ../..

# 3. Build the node agent (it's Linux-only — cross-compile from anywhere)
GOOS=linux GOARCH=amd64 go build -o /tmp/sekhmet-agent ./node-agent/cmd/agent

# 4. Set up the Brain's Python environment
python -m venv brain/.venv
brain/.venv/Scripts/pip install -r brain/requirements.txt   # Windows
# brain/.venv/bin/pip install -r brain/requirements.txt     # Linux/macOS

# 5. Seed the Neo4j test-fleet topology and ingest the starter runbooks
brain/.venv/Scripts/python.exe -m sekhmet.graphdb.seed
brain/.venv/Scripts/python.exe -m sekhmet.rag.ingest_runbooks

# 6. Start the Brain (gRPC on :7443, admin channel on :7444 loopback, dashboard on :8080)
brain/.venv/Scripts/python.exe brain/app.py

Then drop the built agent + certs into a target container and run it (see deploy/certs/target-1-agent-config.yaml for the config a containerized agent needs — note the server_name_override for mTLS across host.docker.internal).

Open http://localhost:8080 for the fleet dashboard.

Running the 3 canonical demo incidents

Actually exhausting fs.file-max for a real FD-exhaustion demo would be unsafe (it's a host-wide, non-namespaced kernel counter shared by every container). Instead, brain/tests/integration/trigger_incident.py drives a synthetic-but-realistic metric batch through the exact same code path a real node agent's stream would — detection, diagnosis, planning, the policy gate, real dispatch to the real connected node agent, and verification:

brain/.venv/Scripts/python.exe brain/tests/integration/trigger_incident.py --host target-1 --type fd            # LOW risk, auto-approved
brain/.venv/Scripts/python.exe brain/tests/integration/trigger_incident.py --host target-1 --type memory         # MEDIUM risk, auto-approved
brain/.venv/Scripts/python.exe brain/tests/integration/trigger_incident.py --host target-1 --type cpu            # HIGH risk, pauses for approval
brain/.venv/Scripts/python.exe brain/tests/integration/trigger_incident.py --host target-1 --type connections    # MEDIUM risk

For a HIGH-risk plan, approve or reject it via the CLI (this resumes the paused LangGraph run against the Brain's live connections, even across a Brain restart in between):

brain/.venv/Scripts/python.exe brain/cli/approve.py list
brain/.venv/Scripts/python.exe brain/cli/approve.py approve <plan_id>

Observability

LangFuse is auto-bootstrapped by deploy/compose/docker-compose.yml's LANGFUSE_INIT_* env vars on first boot — an org, project, user, and a fixed dev API key pair are created automatically. brain/sekhmet/config.py defaults to those same keys, so tracing works with zero manual setup through the LangFuse web UI. (Note: the server image is langfuse/langfuse:2, Postgres-only; requirements.txt deliberately pins langfuse<3 on the Python side, since the v3 SDK only speaks OTLP and needs a ClickHouse-backed v3 server to ingest into — mixing them silently drops every trace.)

Every incident gets one LangFuse trace, named sekhmet-incident, keyed by the incident's own UUID, with one span per graph node — this is what "what did the agent see, recall, conclude, and do" actually looks like for a real run (captured from a real trace produced by this system):

trace: sekhmet-incident  (id = 166e5e48-1c1c-4880-907e-20fcc0920017)
 ├─ detect    10:19:06.537 → 10:19:06.594   {signature, severity}
 ├─ enrich    10:19:07.099 → 10:19:07.150   {history_samples: 12}
 ├─ recall    10:19:07.666 → 10:19:09.182   {similar_cases, runbook_hits, blast_radius}
 ├─ diagnose  10:19:09.285 → 10:20:38.609   → {root_cause, confidence, reasoning}
 ├─ plan      10:20:39.090 → 10:20:45.485   → {action: RaiseUlimitNofile, params, rationale}
 ├─ gate      10:20:45.745 → 10:20:45.745   {gate_status: auto_approved}
 ├─ execute   10:20:46.248 → 10:20:46.330   {status: rejected, message: "sysctl key not in allowlist: ..."}
 ├─ verify    10:20:46.835 → 10:21:06.953   {verified: true, attempt: 1}
 └─ record    10:21:07.435 → 10:21:07.482   {final_status: resolved}

Every incident's detail panel in the dashboard (http://localhost:8080, click any incident card) links straight to its full trace with a "View full trace in LangFuse ↗" button — or browse http://localhost:3000 directly (the auto-created login is admin@sekhmet.local / sekhmet-dev-password123, also in the compose file's LANGFUSE_INIT_USER_* vars).

The dashboard itself shows fleet health, pending approvals, and the incident timeline with the same per-incident detail panel — see Screenshots above. It also supports a deep link, http://localhost:8080/?expand=first, which auto-opens the most recent incident's detail panel — handy for sharing a link straight to "here's what it just did."

Tests

# Node agent (Linux-only package: syscall.Kill, golang.org/x/sys/unix — run in a Linux container)
docker run --rm -v "$(pwd):/src" -w /src -e GOTOOLCHAIN=auto golang:1.25 go test ./node-agent/...

# Brain — unit tests (fast, no infra needed)
brain/.venv/Scripts/python.exe -m pytest brain/tests/ -q --ignore=brain/tests/integration

# Brain — integration tests (needs `docker compose up -d`; drives the real
# pipeline end-to-end for each canonical incident shape, asserting the full
# detect→diagnose→plan→gate→execute→verify audit trail; skips cleanly if
# Postgres isn't reachable)
brain/.venv/Scripts/python.exe -m pytest brain/tests/integration/test_end_to_end.py -v

Regenerating proto stubs

# Go
tools/protoc/bin/protoc.exe -I proto \
  --go_out=node-agent/internal/grpc/sekhmetpb --go_opt=paths=source_relative \
  --go-grpc_out=node-agent/internal/grpc/sekhmetpb --go-grpc_opt=paths=source_relative \
  proto/sekhmet.proto

# Python
brain/.venv/Scripts/python.exe -m grpc_tools.protoc -I proto \
  --python_out=brain/sekhmet/grpc/sekhmetpb \
  --grpc_python_out=brain/sekhmet/grpc/sekhmetpb \
  --pyi_out=brain/sekhmet/grpc/sekhmetpb \
  proto/sekhmet.proto
# then fix the generated grpc file's import to `from . import sekhmet_pb2 as sekhmet__pb2`

About

Self-hosted, agentic AI platform that autonomously monitors and heals a fleet of Linux servers — LangGraph multi-agent reasoning, gRPC/mTLS node agents, and a whitelist + human-approval safety model where the LLM proposes and deterministic code disposes.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages