Skip to main content

deskd

An orchestration engine for multi-agent desks. deskd owns the part that is hard and domain-agnostic: knowing which agents are alive, what they're doing, what's queued for them, and — the difficult bit — reliably waking the right agent at the right time and proving the message actually landed.

Your agents do the domain work. deskd does everything else.

Built for headless Claude Code agents (claude -p + a PostToolUse hook), but the engine is a plain Python package over SQLite with no hard dependency on any particular agent runtime.


Why

Multi-agent systems usually rot in the same three places:

  1. Agents poll. Every agent runs its own sleep/wake loop, burning tokens to discover there's nothing to do — and still missing the thing that mattered.
  2. Messages vanish. "I sent it" ≠ "they read it." Nothing distinguishes notified from read, so a stuck message is invisible until someone notices hours later.
  3. Nobody knows what's running. Two sessions of the same role stomp each other; a crashed agent looks identical to an idle one.

deskd's position: agents must never manage their own waking. They end their turn and the orchestrator wakes them — on a timer, on a calendar, on a custom watcher, on a message, or on an escalation ladder when a wake doesn't land.

What you get

Presence One live session per role, enforced by a role-scoped flock. Heartbeats from the in-session hook; crash-safe (the kernel releases the lock).
Unified inbox Every notification — alerts, signals, system events, meeting messages — lands in one queue per role, with per-key dedup so a re-firing alert never piles up.
Wake orchestration Collect demand → route by presence → record the attempt → verify the loop closed → escalate. A wake that doesn't land climbs: in-session hook → resume → spawn → human (Discord/email) → a red badge on the supervisor console that never times out.
Self-service wake hooks An agent registers its own wakes: --at (one-shot), --every (interval), --cron (calendar, DST-correct), or --probe (your own watcher function — return a dict and it wakes you).
Delivery ledger Per message × recipient: queued → notified → read. Past SLA and unread with nobody reacting = overdue — surfaced red. Rows are a projection of durable messages, so a delivery can't be silently lost.
Bounded meetings Multi-agent meetings with check-in/quorum, mandatory 1:1 replies with an SLA, message budgets, and a mutual termination handshake. Bounded by construction — no infinite agent chatter.
Cross-session tasks Work items that outlive a session. Soft deadlines (due_at) sort to the top but never wake anyone; only priority=urgent does.
Session lifecycle Intraday continuity, cross-day rollover: wind the old session down with a handoff, start fresh the next day.
Supervisor console A web board (live status + queue + hooks + wake activity), a per-agent detail page with full execution history, and a meetings console — behind an access-code or Ed25519 trusted-device gate.

Install

pip install "deskd[web]"     # from PyPI: engine + web console
pip install -e ".[web]"      # or from a checkout, for development

Quickstart

Describe your desk in a module that defines configure_deskd():

# myapp/desk.py
from deskd.config import RoleSpec, PromptBuilder, configure

class MyPrompts(PromptBuilder):
    def bootstrap(self, role: str) -> str:
        return f"Load the myapp skill, declare role={role}, follow its playbook."

def configure_deskd():                        # deskd calls this at startup
    configure(
        roles=(
            RoleSpec("researcher", "Researcher", ("research", "review")),
            RoleSpec("operator",   "Operator",   ("execution",), {"can_execute": True}),
        ),
        timezone="America/New_York",
        inbox_sources=("alert", "signal", "system", "meeting", "supervisor"),
        probe_allowlist=("myapp.watchers",),   # empty = no probes may run
        prompt_builder=MyPrompts(),
    )

Point deskd at it with DESKD_CONFIG_MODULE. Every deskd process — the CLI, deskd serve, the cron driver — imports that module and calls configure_deskd() before it touches the engine, so your roles are registered everywhere. Without it a deskd process starts empty (no roles) and every role-scoped command is rejected.

export DESKD_CONFIG_MODULE=myapp.desk         # (myapp must be importable — on PYTHONPATH)

deskd serve                                   # supervisor console on 127.0.0.1:8000
deskd status set --role operator --activity "watching the queue"
deskd inbox enqueue --for operator --source alert --title "threshold crossed" --priority urgent
deskd wake sources --role operator            # what can wake me, and how to change it

Wake the desk from cron (the driver is the only thing that spawns sessions):

# cron has its own environment — set both vars on the line (or in the crontab header)
* * * * * DESKD_CONFIG_MODULE=myapp.desk DESKD_WAKE_EXECUTE=1 /path/to/deskd/scripts/cron/wake_orchestrator.sh

It is dry-run by default — schedule it, watch the log, then set DESKD_WAKE_EXECUTE=1 when the decisions look right.

Agents schedule themselves — declaratively

# a calendar wake (weekday 06:15, in your configured tz)
deskd hook add --for operator --title "daily digest" --cron "15 6 * * *"

# your own watcher algorithm: return a dict -> it wakes you
deskd hook add --for operator --title "queue depth watch" \
  --probe myapp.watchers:queue_depth --every 600
# myapp/watchers.py — a probe may observe and notify. Nothing else.
def queue_depth():
    n = measure()
    if n > 100:
        return {"title": f"queue at {n}", "priority": "urgent"}
    return None          # None = don't wake anyone

Three consecutive probe errors auto-disable the hook and notify its owner — a broken watcher can't rot silently or stall the tick.

Design notes

Headless sessions can't be interrupted mid-turn. So "deliver to the agent" means two things: while it's running, its PostToolUse hook surfaces the queue into context; while it's idle, the orchestrator resumes its session with the queued items as the prompt. "Current session" = a resumable session id, not a live process.

Storage is SQLite (WAL) and it is the only source of truth. No broker, no daemon holding state. Every tick rebuilds its decisions from the DB, so a crashed orchestrator self-heals on the next tick. SQLite can't wake a dormant process — the engine doesn't pretend otherwise; it makes every wake attempt an auditable row with a closed loop and an escalation path.

Nothing here executes your domain. The engine wakes agents and delivers notifications. It never acts as an agent, and it has no path to your side-effecting systems.

Security

  • The supervisor is not an agent role: agent APIs reject it, and supervisor actions only enter through the authenticated web adapter.
  • simple mode = an access code (convenience, trusted host). signed mode = short-lived Ed25519 assertions from a trusted device; the public key path is fixed at /etc/deskd/supervisor_ed25519.pub, must be root-owned, and is deliberately not environment-overridable — an agent must not be able to point verification at a key it wrote. Keep the private key off the host.
  • Never hardcode the access code into a client/static file. A pre-filled credential in page source is the credential. (Ask us how we know.)
  • Probes only import from your explicit probe_allowlist. Empty = deny all.

See docs/security.md.

Docs

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

deskd-0.1.5.tar.gz (168.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

deskd-0.1.5-py3-none-any.whl (148.8 kB view details)

Uploaded Python 3

File details

Details for the file deskd-0.1.5.tar.gz.

File metadata

  • Download URL: deskd-0.1.5.tar.gz
  • Upload date:
  • Size: 168.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for deskd-0.1.5.tar.gz
Algorithm Hash digest
SHA256 fd4cd6feb1f059d668ed5407fb2cf18b7f8fb76b44af9af3d7f9cb4147182cc1
MD5 c341dbc94ff4017d16cd764832832003
BLAKE2b-256 a46fa92ddd008c187ecd52fa3e46b6af7c39597673198d22345f5c79fcdad1b0

See more details on using hashes here.

Provenance

The following attestation bundles were made for deskd-0.1.5.tar.gz:

Publisher: release.yml on hongdp/deskd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file deskd-0.1.5-py3-none-any.whl.

File metadata

  • Download URL: deskd-0.1.5-py3-none-any.whl
  • Upload date:
  • Size: 148.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for deskd-0.1.5-py3-none-any.whl
Algorithm Hash digest
SHA256 e9b60f77d337f224c880709d08f47e723d506c66c796e86409ea1e9888095afb
MD5 c640ea34076403130d5795d2d5d69669
BLAKE2b-256 48dc26e06c0db54c2b5b0701008934dd89cd49e62e7d0438d3fe11ae4d72e1fb

See more details on using hashes here.

Provenance

The following attestation bundles were made for deskd-0.1.5-py3-none-any.whl:

Publisher: release.yml on hongdp/deskd

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page