Back to Blog
technical-referenceagenticcontrol-planerunbookdockeroperations

Local Agentic Control Plane Runbook

Operational runbook for the local agentic control plane: starting services, monitoring agent health, debugging tool calls, and log inspection.

February 22, 2026·7 min read

Local Agentic Control Plane Runbook

Environment: Mac Mini agent runner, Mac Studio Ollama/Qwen model server, Matrix/Element approval channel, MacBook/iPhone operator clients.

Purpose: Run local spec-driven agent tasks with human approval before execution.


1. System Overview

This runbook documents the local agentic control plane built across your Mac Mini, Mac Studio, Matrix/Element, and iPhone.

The system implements this core loop:

Task file created
  -> watcher detects task
  -> approval_required=true sends it to pending
  -> Matrix notification appears on phone
  -> user replies approve <file.md>
  -> listener runs approve.sh
  -> file returns to inbox with approval_required=false
  -> watcher runs spec_runner.py
  -> Qwen on Mac Studio generates implementation plan
  -> output files are written in workspace

This is a local, human-in-the-loop, spec-driven agent execution system.


2. Machine Roles

Machine Role Main Components
Mac Mini Always-on control plane and agent runner watcher.py, spec_runner.py, Matrix listener, shell scripts
Mac Studio Local AI model server and Matrix homeserver Ollama, Qwen models, Matrix Synapse
MacBook Pro Operator workstation Terminal, VS Code, Element
iPhone Mobile approval surface Element app
DGX Future heavy compute worker GPU jobs, model benchmarks, inference workloads

The design intentionally keeps the Mac Mini as the stable runner so the MacBook Pro can move around and the Mac Studio can preserve memory for models.


3. Directory Structure

~/agentic/
├── configs/
│   ├── agent.env
│   └── hosts.env
├── inbox/
│   └── pending/
├── logs/
├── outbox/
│   └── rejected/
├── repos/
├── runner/
│   ├── watcher.py
│   ├── spec_runner.py
│   └── matrix_approval_listener.py
├── scripts/
│   ├── approve.sh
│   ├── reject.sh
│   ├── pending.sh
│   ├── status.sh
│   └── send_matrix.sh
├── secrets/
│   └── matrix-bot.env
└── workspaces/
    └── mini-control-plane/
        ├── .venv/
        ├── .project/
        │   └── specs/
        │       ├── execution-plan.md
        │       └── tasks.md
        ├── hello.txt
        └── agent-summary.md

Directory Meaning

Directory Meaning
inbox/ New task trigger files arrive here
inbox/pending/ Approval-required tasks wait here
outbox/rejected/ Rejected tasks are archived here
runner/ Python programs that drive the agent loop
scripts/ Shell utilities used by the control plane
configs/ Non-secret shared configuration
secrets/ Matrix bot token and room settings
workspaces/ Actual project execution directories
logs/ Watcher and future run logs

4. Configuration Files

~/agentic/configs/hosts.env

# Tailscale MagicDNS hostnames
MACBOOK_HOST=your-macbook-hostname
MACMINI_HOST=your-mac-mini-hostname
MACSTUDIO_HOST=your-mac-studio-hostname
IPHONE_HOST=your-iphone-hostname

# Service endpoints
OLLAMA_BASE_URL=http://your-mac-studio-hostname:11434
MATRIX_HOMESERVER=http://your-mac-studio-hostname:8008
MATRIX_CLIENT_URL=http://your-mac-studio-hostname:8008
AGENT_RUNNER_HOST=your-mac-mini-hostname

# Paths
AGENTIC_HOME=$HOME/agentic
WORKSPACE_ROOT=$HOME/agentic/workspaces
REPO_ROOT=$HOME/agentic/repos
LOG_DIR=$HOME/agentic/logs
INBOX_DIR=$HOME/agentic/inbox
OUTBOX_DIR=$HOME/agentic/outbox

~/agentic/configs/agent.env

source $HOME/agentic/configs/hosts.env

OLLAMA_MODEL=qwen2.5-coder:32b
AGENT_MODE=local
SAFE_MODE=true

~/agentic/secrets/matrix-bot.env

MATRIX_BOT_USER=@agentbot:<your-tailscale-domain>
MATRIX_BOT_TOKEN=<ROTATE_AND_STORE_REAL_TOKEN_HERE>
MATRIX_ROOM_ID=<your-matrix-room-id>

Secure it:

chmod 600 ~/agentic/secrets/matrix-bot.env

Security note: the Matrix bot token is equivalent to a password. Rotate it if it was exposed in logs, screenshots, chat, or Git.


5. Core Programs

5.1 watcher.py

Purpose:

  • Watches ~/agentic/inbox for .md trigger files.
  • Parses fields such as workspace, action, and approval_required.
  • If approval is required, moves the task into ~/agentic/inbox/pending and sends a Matrix message.
  • If approval is not required, runs spec_runner.py.

Important behavior:

approval_required: true  -> move to pending + notify Matrix + stop
approval_required: false -> execute spec_runner.py

Run it:

cd ~/agentic/runner
python3 watcher.py

Expected output:

[*] Watcher started

5.2 spec_runner.py

Purpose:

  • Reads:
~/agentic/workspaces/mini-control-plane/.project/specs/execution-plan.md
  • Sends the plan to Qwen through Ollama on Mac Studio.
  • Creates hello.txt.
  • Captures git status --short.
  • Writes agent-summary.md.

Run manually:

source ~/agentic/configs/agent.env
~/agentic/workspaces/mini-control-plane/.venv/bin/python ~/agentic/runner/spec_runner.py

Expected files:

~/agentic/workspaces/mini-control-plane/hello.txt
~/agentic/workspaces/mini-control-plane/agent-summary.md

5.3 matrix_approval_listener.py

Purpose:

  • Connects to Matrix using the bot token.
  • Listens to the agent control room.
  • Watches for messages from the human user.
  • Handles commands:
approve <file.md>
reject <file.md>

Run it:

cd ~/agentic/runner
~/agentic/workspaces/mini-control-plane/.venv/bin/python matrix_approval_listener.py

Expected output:

[*] Matrix approval listener started
[*] Commands: approve <file.md> | reject <file.md>

6. Shell Scripts

6.1 approve.sh

Approves a pending file by changing:

approval_required: true

to:

approval_required: false

Then it moves the file from inbox/pending back to inbox, where the watcher executes it.

Usage:

~/agentic/scripts/approve.sh phone-approval-test.md

6.2 reject.sh

Moves a pending file to rejected storage.

Usage:

~/agentic/scripts/reject.sh test-reject.md

Rejected files go to:

~/agentic/outbox/rejected/

6.3 pending.sh

Lists pending approvals.

~/agentic/scripts/pending.sh

6.4 status.sh

Shows pending approvals, rejected files, and recent watcher logs.

~/agentic/scripts/status.sh

6.5 send_matrix.sh

Sends a Matrix notification.

~/agentic/scripts/send_matrix.sh "TEST FROM MINI"

Expected: message appears in Element.


7. Starting the Full System

Use two persistent terminals or tmux panes.

Terminal 1: watcher

cd ~/agentic/runner
python3 watcher.py

Terminal 2: Matrix listener

cd ~/agentic/runner
~/agentic/workspaces/mini-control-plane/.venv/bin/python matrix_approval_listener.py

Optional third terminal for operator commands:

~/agentic/scripts/status.sh

8. End-to-End Approval Test

Create a trigger file:

cat > ~/agentic/inbox/phone-approval-test.md <<'TASK'
workspace: mini-control-plane
action: run
approval_required: true
TASK

Expected watcher output:

[+] Processing /Users/<your-username>/agentic/inbox/phone-approval-test.md
[>] workspace=mini-control-plane action=run
[>] approval_required=true
[!] Waiting for approval → /Users/<your-username>/agentic/inbox/pending/phone-approval-test.md

Expected Matrix message:

Approval required: phone-approval-test.md

Approve from Element/iPhone:

approve phone-approval-test.md

Expected listener output:

[+] Approving phone-approval-test.md
Approved and moved back to inbox: /Users/<your-username>/agentic/inbox/phone-approval-test.md

Expected watcher output:

[+] Processing /Users/<your-username>/agentic/inbox/phone-approval-test.md
[>] workspace=mini-control-plane action=run
[>] approval_required=false
[✓] Agent run complete

Verify generated files:

cat ~/agentic/workspaces/mini-control-plane/hello.txt
cat ~/agentic/workspaces/mini-control-plane/agent-summary.md

9. Reject Test

Create a pending request:

cat > ~/agentic/inbox/test-reject.md <<'TASK'
workspace: mini-control-plane
action: run
approval_required: true
TASK

Reject from Element:

reject test-reject.md

Or reject locally:

~/agentic/scripts/reject.sh test-reject.md

Verify:

ls -la ~/agentic/outbox/rejected

10. Troubleshooting

python: command not found

Use python3 or the venv Python:

~/agentic/workspaces/mini-control-plane/.venv/bin/python

ModuleNotFoundError: No module named 'requests'

The wrong Python interpreter is being used. Run the script with the workspace venv Python.

Matrix message does not show

Check:

ls -la ~/agentic/scripts/send_matrix.sh
cat ~/agentic/secrets/matrix-bot.env
~/agentic/scripts/send_matrix.sh "TEST FROM MINI"

Also ensure your Element user has joined the Matrix room.

agent-summary.md missing

Make sure spec_runner.py ends with:

(project / "agent-summary.md").write_text(summary)
print(summary)

Duplicate pending behavior

If you create the same test file multiple times while watcher is running, it can process multiple copies. Use unique names or clear pending/rejected folders before testing.


11. Cleanup

Remove accidental file if present:

cd ~/agentic/workspaces/mini-control-plane
rm -f send_matrix.she

Check status:

~/agentic/scripts/status.sh

12. Implemented Improvements

The following from the original list are now built:

  • Stage-aware gating (stage: dev/staging/prod) — see Section 16
  • Gradio dashboard — see Section 15
  • Docker containerization — see Section 14

Remaining items:

  1. Rotate Matrix bot token.
  2. Add per-run IDs.
  3. Add success/failure Matrix notification after execution.
  4. Add status Matrix command.
  5. Add approve without filename to approve the latest pending task.
  6. Add per-run log files.
  7. Add Git branch creation before execution.
  8. Add Git diff summary after execution.
  9. Add command allowlist.
  10. Add DGX worker integration.

13. Mental Model

inbox/          = new requests
pending/        = waiting for human approval
approve.sh      = human says yes
reject.sh       = human says no
watcher.py      = event loop
spec_runner.py  = task executor
Qwen/Ollama     = reasoning engine
Matrix/Element  = phone approval channel
dashboard.py    = browser control panel
docker/         = security sandbox + portable stack

14. Docker Operations

The Docker setup runs three services: runner (watcher.py), matrix-listener, and dashboard (Gradio).

Start the full stack

cd ~/agentic/docker
docker-compose up -d

Stop the stack

docker-compose down

View logs from a service

docker-compose logs -f runner
docker-compose logs -f matrix-listener
docker-compose logs -f dashboard

Security model

The runner container mounts ONLY:

Host path Container path Access
~/agentic/inbox /inbox read-write
~/agentic/outbox /outbox read-write
~/agentic/workspaces /workspaces read-write
~/agentic/logs /logs read-write
~/agentic/configs /configs read-only
~/agentic/secrets /secrets read-only

The Docker socket is NOT mounted. The AI agent cannot spawn new containers or access the host filesystem outside the listed mounts.

Environment variables

Copy the template before first run:

cp ~/agentic/docker/.env.example ~/agentic/docker/.env

Edit .env to set OLLAMA_BASE_URL and MATRIX_HOMESERVER if your Tailscale names differ from defaults.


15. Gradio Dashboard

The dashboard runs at http://localhost:7860 (or the Mac Mini's Tailscale IP on port 7860).

Start manually (without Docker)

~/agentic/workspaces/mini-control-plane/.venv/bin/python ~/agentic/runner/dashboard.py

Panels

Panel What it shows How to use
Process Status Whether watcher.py and matrix_approval_listener.py are running Click Start/Stop buttons
Pending Approvals Files in inbox/pending/ Select a file, click Approve or Reject
Live Log Last 100 lines of watcher.log, refreshes every 3 seconds Read-only
Config Viewer Contents of hosts.env and agent.env Secrets are masked with ****

Approve a pending task from the dashboard

  1. Open http://localhost:7860
  2. In the Pending Approvals panel, select the file name
  3. Click Approve — equivalent to running approve.sh <filename> or typing approve <filename> in Element

16. SDLC Stage Reference

The watcher now reads a stage field from trigger files and auto-derives whether approval is needed.

Stage behavior

Stage Approval required Use case
dev No — runs immediately Experimental tasks, drafting, sandbox work
staging Yes — waits for approval Integration tests, shared environment changes
prod Yes — waits for approval Production deploys, DGX jobs, pushing to main

Trigger file format with stage

workspace: mini-control-plane
action: run
stage: staging

You do not need to set approval_required explicitly. The stage drives the gate.

To override the stage default, set approval_required explicitly:

workspace: mini-control-plane
action: run
stage: prod
approval_required: false

This would run a prod-stage task without approval — use only when you have confirmed the task is safe.

Default stage

Set in ~/agentic/configs/agent.env:

DEFAULT_STAGE=dev

If a trigger file does not include a stage field, the system uses DEFAULT_STAGE.

Example: dev task (no approval)

cat > ~/agentic/inbox/dev-test.md <<'TASK'
workspace: mini-control-plane
action: run
stage: dev
TASK

Expected: watcher processes immediately, no Matrix notification.

Example: staging task (approval required)

cat > ~/agentic/inbox/staging-deploy.md <<'TASK'
workspace: mini-control-plane
action: run
stage: staging
TASK

Expected: watcher moves to pending, sends Matrix notification, waits for approval.