AZ Desk is a two-way task and delivery board between an AI agent (Claude) and a human owner, plus a small agentic-loop engine that lets the agent act on the board on its own.
The agent writes task cards — status, deliverables, a plain-prose progress note. The owner opens the board (e.g. from a phone), taps a status, leaves a 👍/👎/🤔 verdict, or types a free-text note. Every feedback action is persisted and appended to a durable inbox. A worker tails that inbox and lets the agent act on the feedback and report back — so the owner's feedback visibly changes the board. That round trip is the whole point: it's a feedback loop, not a one-way to-do list.
This repository is an extracted, generalized version of a personal setup. All personal data, credentials, hardcoded hosts, and project-specific content have been removed; the loop that shipped with one bespoke project is replaced by a neutral example.
┌───────────────────────────────────────────┐
owner's phone ──►│ board.html (single-page UI, no build) │
└───────────────┬───────────────────────────┘
│ fetch /api/*
┌───────────────▼───────────────────────────┐
│ server.py (Flask) │
│ GET /api/tasks read the board │
│ POST /api/feedback status/sat/note/due │
│ POST /api/update agent write-back │
│ POST /api/quickadd /api/smartadd │
│ POST /api/project-chat /api/board-chat │
│ single writer of tasks.json (atomic) │
└───┬───────────────┬───────────────┬────────┘
appends a line │ │ appends │ chat inbox lines
feedback_inbox │ │ inboxes │
┌───▼───┐ ┌────────▼──────┐ ┌─────▼──────────────┐
│receipt│ │ act_worker │ │ board_chat_worker │
│worker │ │ tails feedback│ │ project_chat_worker│
│(notify│ │ spawns claude │ │ drive the engine │
│ ack) │ │ writes back │ │ on a chat message │
└───────┘ └───────┬───────┘ └─────────┬──────────┘
│ HTTP /api │ run_*_message
▼ ▼
┌──────────────────────────────────────┐
│ az_desk/engine (vendored) │
│ loop_runner gather→maker→gate→ │
│ land→report→checkpoint│
│ loop_specs per-project LoopSpec │
│ project_memory packed, ranked memory│
│ maker != checker, fail-closed gate │
└──────────────────────────────────────┘
Pieces:
server.py— the Flask board server. It is the only writer oftasks.json(atomic temp-file replace). Workers and the engine mutate the board only through its HTTP API, so there is one consistent write path.board.html— a single self-contained page (no build step) the owner interacts with. It talks to the server with relative/api/...paths.act_worker.py— tails the feedback inbox; for each actionable note it spawns a headlessclaudethat does the safe, doable part and writes its progress back via/api/update. Maintains a per-task warm session so a follow-up note resumes prior context.receipt_worker.py— sends the owner a quick "got it" receipt for each feedback action (optional; only does anything if you configure a notifier).board_chat_worker.py/project_chat_worker.py— drive the loop engine when the owner chats with the whole board (the global manager) or with one project, respectively.board_nudge.py— a daily focus digest + (optional, gated) event nudges, computed from board state. Optional notifier.reminders_sync.py— an optional, macOS-only, dormant-by-default mirror of hard deadlines into Apple Reminders via aremindctl-compatible CLI you supply.az_desk/engine/— the vendored agentic-loop engine (self-contained; does not import any private package). A loop runs six stages — gather → maker → independent gate → land → report → checkpoint. The maker is a headlessclaudewhose proposed board mutations are validated by a second, independent checker pass (maker ≠ checker), which is fail-closed and strictly subtractive.project_memory.pypacks a per-project workspace memory under a character budget, ranking snippets with scikit-learn's TF-IDF (with a pure-Python fallback).
- The owner taps a status / leaves a note on a card →
POST /api/feedback. - The server persists it to
tasks.jsonand appends a line tofeedback_inbox.jsonl. act_worker.py(if enabled) picks up the note, spawns a headlessclaudewith a self-contained prompt, and the agent writes its progress back viaPOST /api/update.- The board now shows the agent's reply in the card's thread.
Chatting with a project or the whole board works the same way, but the worker drives the loop
engine (run_loop_message / run_board_message) instead of a one-shot prompt.
pip install -r requirements.txtPython 3.9+ is fine. scikit-learn is optional (used for memory ranking; the engine falls
back to a pure-Python ranker without it).
Seed the board from the sample data:
cp sample_tasks.json tasks.jsonpython server.py # serves the board on http://127.0.0.1:8770Open http://127.0.0.1:8770/ in a browser. The board binds loopback by default. To reach it
from another device, put it behind your own network boundary (a VPN such as Tailscale, an SSH
tunnel, or a reverse proxy with auth) — there is no auth layer in this server, so never
expose it on 0.0.0.0 to an untrusted network. Override the bind with
BOARD_LISTEN_HOST / BOARD_LISTEN_PORT.
The workers are gated OFF by default so you can run them dormant and inspect the logs first.
# auto-act on feedback notes (spawns claude); requires the `claude` CLI on PATH
ACT_ENABLED=1 python act_worker.py
# drive the loop engine from the chat boxes
PROJECT_CHAT_ENABLED=1 python project_chat_worker.py
BOARD_CHAT_WORKER_ENABLED=1 python board_chat_worker.py| Env var | Used by | Default | Meaning |
|---|---|---|---|
BOARD_LISTEN_HOST / BOARD_LISTEN_PORT |
server | 127.0.0.1 / 8770 |
bind address |
AZ_DESK_VAULT |
server, act_worker | cwd | root for deliverable file paths + spawned-agent cwd |
AZ_DESK_BOARD_API / BOARD_BASE / BOARD_API_BASE |
workers, engine | http://127.0.0.1:8770 |
board API base URL |
AZ_DESK_HOME |
engine | ~/.az-desk |
engine state / logs / project memory root |
AZ_DESK_CLAUDE_CMD |
engine | claude |
the claude command to spawn (a test seam) |
AZ_DESK_AGENTS_DIR |
engine | (unset) | optional agent-role prompt catalog |
AZ_DESK_NOTIFY_CMD / AZ_DESK_NOTIFY_USER |
workers, nudge | (unset) | optional outbound-notify CLI (--user-id <id> --text <text>); unset = log-only |
ACT_ENABLED |
act_worker | 0 |
spawn claude on feedback notes |
PROJECT_CHAT_ENABLED / BOARD_CHAT_WORKER_ENABLED |
chat workers | 0 |
drive the engine from chat |
REMINDERS_ENABLED / REMINDCTL_BIN |
reminders_sync | 0 / remindctl |
optional Apple Reminders mirror |
The repo ships no notifier and no credentials. The notify-based pieces
(receipt_worker, board_nudge) only do anything when you point AZ_DESK_NOTIFY_CMD at a
CLI you supply; otherwise they log what they would have sent.
pytestTests run with no network and no real claude: the engine's maker/gate spawns are routed to a
fake binary via AZ_DESK_CLAUDE_CMD, and every board/notify effect is a recording mock.
This is an extracted and generalized version of a personal "delivery board" + loop engine.
The original was tied to one user's projects, hosts, and credentials; all of that has been
removed and replaced with neutral examples. The single example loop (az_desk/engine/loop_specs.py,
prompts/example.py) reads a plain-text chat transcript and lands tasks on the board — it is a
template you replace with your own gather hook and prompt pack.