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/inboxfor.mdtrigger files. - Parses fields such as
workspace,action, andapproval_required. - If approval is required, moves the task into
~/agentic/inbox/pendingand 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:
- Rotate Matrix bot token.
- Add per-run IDs.
- Add success/failure Matrix notification after execution.
- Add
statusMatrix command. - Add
approvewithout filename to approve the latest pending task. - Add per-run log files.
- Add Git branch creation before execution.
- Add Git diff summary after execution.
- Add command allowlist.
- 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
- Open
http://localhost:7860 - In the Pending Approvals panel, select the file name
- Click Approve — equivalent to running
approve.sh <filename>or typingapprove <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.