---
title: "Run the MeshKore daemon on a machine"
audience: "An AI coding agent (or operator) installing, starting, keeping alive, checking, stopping or fixing the local MeshKore daemon that the Architect connects to."
purpose: "Exact commands, expected output, and failure → fix. One daemon per machine."
canonical: "https://meshkore.com/reference/agents/daemon.md"
human_version: "https://meshkore.com/architect#daemon"
updated: 2026-10-21
---

# Run the MeshKore daemon

The daemon is **ONE process per machine**. It serves every project on that
machine (requests are routed by the `X-MeshKore-Project: <cluster-id>`
header) and it is what the Architect at `https://architect.meshkore.com`
talks to. You never start one per project.

| Fact | Value |
|---|---|
| Program | one stdlib-only Python file, `daemon.py` (Python ≥ 3.8, no pip) |
| Lives in | `<home>/.meshkore/scripts/daemon.py` — `<home>` is one folder, conventionally `~/meshkore-server` |
| Port | first free in `5570–5589`, sticky (normally `5573`) |
| URLs | `https://daemon.meshkore.com:<port>` (public DNS → `127.0.0.1`) and `https://localhost:<port>` |
| TLS bundle | `<home>/.meshkore/scripts/tls/{fullchain.pem,privkey.pem}` — without it the daemon serves plain HTTP and the browser cockpit cannot reach it |
| Bearer token | `<home>/.meshkore/credentials/portal-token` (minted on first boot, mode 0600) |

Below, `HOME_DIR` is that folder. Set it once:

```bash
HOME_DIR="$HOME/meshkore-server"
```

## 0. Is it already running? (always check first)

```bash
curl -sk https://localhost:5573/health
```

- JSON with `"ok": true` → it is running. Read `port`, `version`, `tls`,
  `endpoint`, and `home` (`home.path` = the install folder, `home.start` /
  `home.stop` = the exact commands for this machine, `home.resident` = a
  service keeps it alive). **Do not start a second one.**
- `cluster_id` in that answer is the daemon's default project (e.g.
  `meshkore-server`), not yours. That is expected.
- Connection refused → not on 5573. Check the whole range before concluding
  it is down:

```bash
for p in $(seq 5570 5589); do curl -sk -m 1 "https://localhost:$p/health" >/dev/null && echo "up on $p"; done
```

## 1. Install (once per machine)

```bash
mkdir -p "$HOME_DIR/.meshkore/scripts"
curl -fsSL https://architect.meshkore.com/reference/cluster/scripts/daemon.py \
  -o "$HOME_DIR/.meshkore/scripts/daemon.py"
head -1 "$HOME_DIR/.meshkore/scripts/daemon.py"   # expect: #!/usr/bin/env python3
```

Put the TLS bundle next to it, in `$HOME_DIR/.meshkore/scripts/tls/`
(`fullchain.pem` + `privkey.pem`, `chmod 600 privkey.pem`). Check that each
file starts with `-----BEGIN` — an HTML page saved under that name is not a
certificate. The daemon refreshes the bundle from its release channel when it
updates itself.

Never put `daemon.py` inside a project repo. Projects are added from the
Architect ("+ Add project" → `POST /projects {"path": …}`); the running daemon
scaffolds their `.meshkore/` folder.

## 2. Way A — start it from a terminal (survives closing the terminal)

A plain `python3 daemon.py` dies with the terminal (and with the VS Code window
that owns it). Start it in its **own session** instead:

```bash
mkdir -p "$HOME_DIR/.meshkore/.runtime/logs"
nohup python3 -c 'import os, sys; os.setsid(); os.execv(sys.executable, [sys.executable] + sys.argv[1:])' \
  "$HOME_DIR/.meshkore/scripts/daemon.py" --root "$HOME_DIR" \
  >>"$HOME_DIR/.meshkore/.runtime/logs/daemon.out" 2>&1 </dev/null &
disown
sleep 3; curl -sk https://localhost:5573/health
```

Works on macOS and Linux. It keeps running after the terminal closes; it does
**not** survive a reboot or logout → use Way B for that.

Logs: `tail -f "$HOME_DIR/.meshkore/.runtime/logs/daemon.out"`.

## 3. Way B — install it as a resident service (survives reboots)

### macOS (launchd)

```bash
PY="$(command -v python3)"
cat > ~/Library/LaunchAgents/com.meshkore.daemon.plist <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
  <key>Label</key><string>com.meshkore.daemon</string>
  <key>ProgramArguments</key><array>
    <string>$PY</string>
    <string>$HOME_DIR/.meshkore/scripts/daemon.py</string>
    <string>--root</string><string>$HOME_DIR</string>
  </array>
  <key>EnvironmentVariables</key><dict>
    <key>PATH</key><string>$PATH</string>
    <key>HOME</key><string>$HOME</string>
  </dict>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>ThrottleInterval</key><integer>10</integer>
  <key>StandardOutPath</key><string>/tmp/meshkore-daemon.log</string>
  <key>StandardErrorPath</key><string>/tmp/meshkore-daemon.log</string>
</dict></plist>
EOF
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.meshkore.daemon.plist
sleep 5; curl -sk https://localhost:5573/health
```

Stop any Way-A daemon first (section 5), or the two fight over the port.
Why each line is there:

- `PATH` is pinned because launchd's PATH is minimal and the daemon spawns
  `claude`, `codex`, `gemini`, `git`…
- The log goes to `/tmp` and there is no `WorkingDirectory`: launchd exits with
  code 78 if it cannot open the log or `chdir`, and `~/Documents` is
  privacy-protected (TCC) from launchd agents.
- **Projects under `~/Documents`, `~/Desktop` or `~/Downloads`:** a launchd
  daemon cannot read them unless the Python binary has Full Disk Access. Symptom:
  `/health` is green but projects answer 500 and the log says
  `Operation not permitted`. Fix: System Settings → Privacy & Security → Full
  Disk Access → add the real interpreter (`ps -o command= -p <daemon pid>`
  shows it; it lives inside `Python.app`), then
  `launchctl kickstart -k gui/$(id -u)/com.meshkore.daemon`. If you cannot grant
  it, use Way A.

`KeepAlive` means killing the process just restarts it. To stop it, unload it
(section 5).

### Linux (systemd user service)

```bash
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/meshkore-daemon.service <<EOF
[Unit]
Description=MeshKore daemon

[Service]
ExecStart=$(command -v python3) $HOME_DIR/.meshkore/scripts/daemon.py --root $HOME_DIR
Environment=PATH=$PATH
Restart=always
RestartSec=10

[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user enable --now meshkore-daemon
loginctl enable-linger "$USER"   # start at boot, without an open login session
```

Logs: `journalctl --user -u meshkore-daemon -f`.

### Windows

No resident recipe is published. Run it from PowerShell with
`py -3 <home>\.meshkore\scripts\daemon.py --root <home>` (or use Task Scheduler
"At log on" with the same command).

## 4. Check it

```bash
curl -sk https://localhost:5573/health          # ok, port, version, tls, home
curl -s  https://daemon.meshkore.com:5573/health # same, WITHOUT -k: proves the certificate the browser will see is valid
dig +short daemon.meshkore.com                   # expect 127.0.0.1
```

macOS: `launchctl print gui/$(id -u)/com.meshkore.daemon | grep state` →
`state = running`. Linux: `systemctl --user status meshkore-daemon`.

## 5. Stop / restart

Graceful (drains running agent turns, then exits — any install):

```bash
curl -sk -X POST https://localhost:5573/shutdown \
  -H "Authorization: Bearer $(cat "$HOME_DIR/.meshkore/credentials/portal-token")"
```

That returns `401` without the token. If it is resident, the service restarts
it — unload the service instead:

- macOS: `launchctl bootout gui/$(id -u)/com.meshkore.daemon` (start again:
  `launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/com.meshkore.daemon.plist`;
  restart in place: `launchctl kickstart -k gui/$(id -u)/com.meshkore.daemon`).
- Linux: `systemctl --user stop meshkore-daemon` / `restart`.
- Way A, if `/shutdown` did not work: `pkill -f "$HOME_DIR/.meshkore/scripts/daemon.py"`.

Agent subprocesses the daemon started run detached and can outlive it. They
carry a `MESHKORE_CONV` environment marker:
`ps eww -o pid=,command= | grep MESHKORE_CONV= | grep -v grep`.

## 6. Uninstall the service

- macOS: `launchctl bootout gui/$(id -u)/com.meshkore.daemon; rm ~/Library/LaunchAgents/com.meshkore.daemon.plist`
- Linux: `systemctl --user disable --now meshkore-daemon; rm ~/.config/systemd/user/meshkore-daemon.service`

The daemon then only runs when started by hand (Way A). Removing
`$HOME_DIR` removes the daemon, its token and its project registry; your
projects' own `.meshkore/` folders stay untouched.

## 7. The cockpit says "The daemon is not running"

The Architect probes `5570–5589` from the browser. Work down this list:

| Check | Result | Fix |
|---|---|---|
| `curl -sk https://localhost:5573/health` (and the range loop in §0) | nothing answers | It is down. Start it: Way A, or `launchctl kickstart -k gui/$(id -u)/com.meshkore.daemon` / `systemctl --user start meshkore-daemon`. If it exits again, read the log. |
| log ends in a traceback / `Address already in use` | crash or port clash | `lsof -nP -iTCP:5570-5589 -sTCP:LISTEN` shows who holds the ports; stop the duplicate daemon. |
| `/health` is OK but `"tls": false` | no TLS bundle | The browser (HTTPS page) cannot call `http://localhost`. Put the bundle in `.meshkore/scripts/tls/` (§1) and restart. |
| `/health` OK with `-k`, but `curl -s https://daemon.meshkore.com:5573/health` (no `-k`) fails with a certificate error | the loopback certificate expired or does not match | The browser refuses a running daemon. Install a current `tls/` bundle next to `daemon.py` and restart. |
| `dig +short daemon.meshkore.com` is not `127.0.0.1` | your DNS/router blocks answers pointing at 127.0.0.1 (DNS-rebinding protection) | Allow `daemon.meshkore.com` in the router/DNS filter, or use another resolver. |
| macOS resident, `/health` OK, projects fail with `Operation not permitted` | Full Disk Access missing | §3 macOS. |
| everything above is fine | browser-side | Reload the cockpit; it reconnects on its own once `/health` answers. |

The cockpit remembers where the daemon lives (from `/health.home`) and prints
the start command for this machine on that screen; a browser that has never
connected shows the generic form, which is section 2 here.

Confirm when done: `curl -sk https://localhost:5573/health`.

## Related

- Standard §10.1–§10.3 (centralized daemon, endpoints, adding projects):
  <https://meshkore.com/standard/10.1.md>, <https://meshkore.com/standard/10.2.md>,
  <https://meshkore.com/standard/10.3.md>
- Human version: <https://meshkore.com/architect#daemon>
