adispatchd

adispatchd

Agent Dispatch Daemon · v0.10.1 · MIT

Turn GitHub issues into supervised runs of your shell scripts and AI agents – declaratively, observably, on your own machine. Aus GitHub-Issues werden überwachte Runs deiner Shell-Skripte und KI-Agenten – deklarativ, nachvollziehbar, auf deinem eigenen Rechner.

npm install -g adispatchd

Node.js ≥ 22.13 · Linux & macOS · SQLite built in (no native add-on) Node.js ≥ 22.13 · Linux & macOS · SQLite eingebaut (kein natives Addon)

Why adispatchd?Warum adispatchd?

Running coding or review agents against an issue tracker usually ends up as a pile of cron jobs and glue scripts: who picks which issue, what happens when an agent crashes, who resets the labels, where are the logs? adispatchd moves those decisions into a few YAML files – GitHub stays the source of truth, workers stay plain programs, and technical failures are kept apart from business results.

Wer Coding- oder Review-Agenten auf einen Issue-Tracker loslässt, landet meist bei einem Haufen Cronjobs und Klebeskripten: Wer nimmt welches Issue, was passiert bei einem Absturz, wer setzt die Labels zurück, wo liegen die Logs? adispatchd verlegt diese Entscheidungen in wenige YAML-Dateien – GitHub bleibt Source of Truth, Worker bleiben normale Programme, und technische Fehler sind von fachlichen Ergebnissen getrennt.

FeaturesFunktionen

GitHub as task sourceGitHub als Aufgabenquelle

Issues and pull requests, including GitHub Projects fields (status, priority, custom fields), native dependencies and the issue's work branch and PR.

Issues und Pull Requests inklusive GitHub-Projects-Feldern (Status, Priorität, eigene Felder), nativen Abhängigkeiten sowie Arbeits-Branch und PR des Issues.

Declarative filtersDeklarative Filter

all/any/not trees and named filters reused across workflows and views. Missing access yields unknown – never a wrong start.

all/any/not-Bäume und benannte Filter für Workflows und Views. Fehlender Zugriff ergibt unknown – nie einen falschen Start.

Priorities & limitsPrioritäten & Limits

Workflow priority first, then item priority rules. Global, per-workflow and per-source concurrency limits, a lock per item and retry blocks.

Erst Workflow-Priorität, dann Item-Prioritätsregeln. Globale, workflow- und sourcebezogene Limits, ein Lock je Item und Retry-Blocks.

Visible conditions & info tilesSichtbare Bedingungen & Info-Kacheln

Named start conditions with their last result in the UI, and dashboard tiles from dashboard.yaml – small scripts run in the background (text, number or percent with thresholds).

Benannte Startbedingungen mit ihrem letzten Ergebnis in der UI und Dashboard-Kacheln aus dashboard.yaml – kleine Skripte, im Hintergrund ausgeführt (Text, Zahl oder Prozent mit Schwellwerten).

Your dashboard, your layoutDein Dashboard, dein Layout

Arrange the dashboard in dashboard.yaml: rows, columns and widths, views as widgets with chosen sections and row limits, and tabs – inactive tabs load nothing, the selected tab is part of the URL.

Das Dashboard in dashboard.yaml anordnen: Reihen, Spalten und Breiten, Views als Widgets mit gewählten Abschnitten und Zeilenlimit, dazu Reiter – inaktive Reiter laden nichts, der gewählte Reiter steht in der URL.

Source, cron & manual triggersSource-, Cron- & manuelle Trigger

Poll GitHub, run on a cron schedule with time zone, or start manually from the UI/API with typed parameters – pick a branch, tag or a value from your own script in the start form. One workflow can serve several repositories.

GitHub abfragen, per Cron mit Zeitzone laufen oder manuell aus UI/API mit typisierten Parametern starten – im Startformular einen Branch, Tag oder Wert aus einem eigenen Skript wählen. Ein Workflow kann mehrere Repositories bedienen.

Lifecycle hooksLifecycle-Hooks

on_start, on_success, on_failure, crash/timeout/cancel hooks, on_result.<outcome> for named results, a catch-all on_error and on_retry_block/on_retry_unblock when a retry block is created or removed.

on_start, on_success, on_failure, Hooks für Absturz/Timeout/Abbruch, on_result.<outcome> für benannte Ergebnisse, der Sammel-Hook on_error sowie on_retry_block/on_retry_unblock, wenn ein Retry-Block entsteht oder aufgehoben wird.

Labels, project fields, commentsLabels, Projektfelder, Kommentare

Move items across your board as runs start and end. Idempotent (read before write), journaled and retried; items are added to the project if needed.

Items wandern beim Start und Ende eines Runs über das Board. Idempotent (erst lesen, dann schreiben), mit Journal und Retries; Items werden bei Bedarf dem Projekt hinzugefügt.

ArtifactsArtefakte

Workflows produce files or folders (execute.artifacts); they get an Artifacts page with file tree, download and delete, and are the items of their own source – so the next workflow processes each artifact exactly once (consumed_by), with a read-only copy in its workspace. No pipeline engine.

Workflows erzeugen Dateien oder Ordner (execute.artifacts); sie erscheinen auf einer Artifacts-Seite mit Dateibaum, Download und Löschen und sind die Items einer eigenen Source – der nächste Workflow verarbeitet jedes Artefakt genau einmal (consumed_by), mit einer schreibgeschützten Kopie im Workspace. Ohne Pipeline-Engine.

Item historyItem-Verlauf

One page per issue or PR: its current state, the path of all runs with their results and a timeline of hooks with what each action changed (labels, project field old → new, comments), retry blocks and UI actions – optionally merged with the GitHub timeline.

Eine Seite je Issue oder PR: aktueller Stand, der Pfad aller Runs mit Ergebnis und eine Zeitleiste der Hooks mit der Wirkung jeder Action (Labels, Projektfeld alt → neu, Kommentare), Retry-Blocks und UI-Aktionen – optional zusammen mit der GitHub-Timeline.

Act as your GitHub AppAls GitHub App handeln

Admins reply to an issue, change labels or set project fields from the web UI as a configured app identity – opt-in per identity, login required, confirmation before sending and an audit log (kept 365 days by default).

Admins antworten auf ein Issue, ändern Labels oder setzen Projektfelder aus der Web-UI als konfigurierte App-Identity – je Identity freizuschalten, nur mit Anmeldung, mit Bestätigung vor dem Senden und Audit-Log (Default 365 Tage aufbewahrt).

Shell or container workersShell- oder Container-Worker

Commands run without a shell in their own process group – or in a throw-away Docker/Podman container with fixed mounts, resource limits and --cap-drop ALL.

Kommandos laufen ohne Shell in eigener Prozessgruppe – oder in einem Wegwerf-Container (Docker/Podman) mit festen Mounts, Ressourcenlimits und --cap-drop ALL.

Token files for long runsToken-Dateien für lange Runs

Short-lived GitHub App tokens as an environment variable or a file (<NAME>_FILE) that the daemon renews atomically and deletes after the run.

Kurzlebige GitHub-App-Tokens als Umgebungsvariable oder als Datei (<NAME>_FILE), die der Daemon atomar erneuert und nach dem Run löscht.

Keep filesDateien sichern

execute.keep saves transcripts and logs (glob patterns) before the workspace is cleaned up – size-limited, never following symlinks, downloadable in the UI.

execute.keep sichert Mitschnitte und Logs (Glob-Muster) vor dem Workspace-Cleanup – mit Größenlimit, ohne Symlinks zu folgen, Download in der UI.

Live web UI & APILive-Weboberfläche & API

Workflows, runs with live output, events, kept files and read-only views across repositories (custom value order, several sort keys, sections with headings), streamed via Server-Sent Events – plus a JSON HTTP API. Optional toolbar links to other local tools such as claude-inspect, in a new tab or embedded.

Workflows, Runs mit Live-Output, Events, gesicherte Dateien und read-only Views über mehrere Repositories (eigene Wertereihenfolge, mehrere Sortierschlüssel, Abschnitte mit Überschriften), live per Server-Sent Events – dazu eine JSON-HTTP-API. Optional Links zu weiteren lokalen Tools wie claude-inspect in der Toolbar, im neuen Tab oder eingebettet.

Server resourcesServer-Ressourcen

A compact strip on the dashboard shows CPU, memory, disk and the busiest processes at a glance; the Resources tab charts CPU over time stacked by the daemon, each run, hooks and the busiest other processes – plus memory and disk. Command names only, never arguments.

Eine kompakte Leiste im Dashboard zeigt CPU, RAM, Platte und die größten Prozesse auf einen Blick; der Reiter „Resources“ zeigt die CPU über die Zeit, gestapelt nach Daemon, jedem Run, Hooks und den größten fremden Prozessen – dazu RAM und Platte. Nur Befehlsnamen, nie Argumente.

One hub for several projectsEin Hub für mehrere Projekte

Run several independent workspaces – each a complete instance with its own config, database and port – under one shared limit. One overview shows every workspace (state, runs, blocked items, slots, server resources); each workspace UI lives at /w/<id>/ on the same port, with one login and roles. A crashed workspace restarts on its own; one systemd unit (service install --hub) covers everything, and an existing instance moves in without copying data.

Mehrere unabhängige Workspaces – je eine vollständige Instanz mit eigener Konfiguration, Datenbank und eigenem Port – laufen unter einem gemeinsamen Limit. Eine Übersicht zeigt jeden Workspace (Zustand, Runs, blockierte Items, Slots, Serverressourcen); die Workspace-UIs liegen unter /w/<id>/ auf demselben Port, mit einer Anmeldung und Rollen. Ein abgestürzter Workspace startet selbst neu; eine systemd-Unit (service install --hub) deckt alles ab, und eine bestehende Instanz zieht ohne Kopieren von Daten ein.

Dry-run & validationDry-Run & Validierung

config validate reports errors with file and line; workflow dry-run explains for every item why it would or would not start – without starting anything.

config validate meldet Fehler mit Datei und Zeile; workflow dry-run erklärt je Item, warum es starten würde oder nicht – ohne etwas zu starten.

Built for operationsFür den Betrieb gebaut

systemd service installer (single instance or hub), drain and cancel-all, crash recovery, atomic config reload and automatic migrations with backup.

systemd-Installer (Einzelinstanz oder Hub), Drain und Cancel-All, Crash-Recovery, atomarer Config-Reload und automatische Migrationen mit Backup.

How it worksSo funktioniert es

adispatchd flow GitHub issues are polled, filtered and prioritised; conditions and limits gate the start; cron and manual triggers enter at the same point; the worker runs as a shell process or container; lifecycle hooks write labels, project fields and comments back to GitHub; logs and events stream to the web UI. cron / manual Cron / manuell web UI / API Weboberfläche / API live via SSE live per SSE GitHub issues · PRs · Projects filters & priorities Filter & Prioritäten conditions & limits Bedingungen & Limits worker shell or container Shell oder Container lifecycle hooks Lifecycle-Hooks on_success · on_result on_error · … exit code + result.json Exit-Code + result.json labels · project fields · comments Labels · Projektfelder · Kommentare
  1. On every planning step the daemon polls each source, evaluates the workflow filter and sorts candidates by priority.
  2. Start conditions – shell checks, limits, locks and retry blocks – decide whether a run may start.
  3. The worker gets a workspace, a JSON context file and, if declared, a token. Its exit code and optional result file decide which hook runs.
  4. Hooks update labels, project fields and comments on the item – so the next workflow (for example a reviewer) can pick it up.
  1. In jedem Planungsschritt fragt der Daemon die Sources ab, wertet den Workflow-Filter aus und sortiert die Kandidaten nach Priorität.
  2. Startbedingungen – Shell-Prüfungen, Limits, Locks und Retry-Blocks – entscheiden, ob ein Run starten darf.
  3. Der Worker bekommt einen Workspace, eine JSON-Kontextdatei und – falls deklariert – ein Token. Exit-Code und optionale Ergebnisdatei bestimmen den Hook.
  4. Hooks ändern Labels, Projektfelder und Kommentare am Item – der nächste Workflow (z. B. ein Reviewer) übernimmt.

One YAML file per workflowEine YAML-Datei pro Workflow

This coder takes issues labelled ready-for-coding whose project status is Todo or Ready, moves them across the board while it works and puts them back in the queue on any technical error.

Dieser Coder nimmt Issues mit Label ready-for-coding, deren Projektstatus Todo oder Ready ist, schiebt sie während der Arbeit über das Board und stellt sie bei jedem technischen Fehler zurück in die Warteschlange.

The worker contractDer Worker-Vertrag

  • Input: a minimal environment plus declared secrets and ADISPATCH_RUN_ID, ADISPATCH_WORKSPACE, ADISPATCH_CONTEXT_FILE, ADISPATCH_RESULT_FILE, …
  • Exit code = technical status: 0 is ok, anything else is crashed.
  • Result file for the business result: {"schema_version":1,"result":"failure","message":"…"}, optionally with an "outcome" for on_result.
  • Events: @@ADISPATCH_EVENT {"type":"status","message":"…"} lines show up live in the UI.
  • Input: Minimalumgebung plus deklarierte Secrets und ADISPATCH_RUN_ID, ADISPATCH_WORKSPACE, ADISPATCH_CONTEXT_FILE, ADISPATCH_RESULT_FILE, …
  • Exit-Code = technischer Status: 0 ist ok, alles andere crashed.
  • Ergebnisdatei für das fachliche Ergebnis: {"schema_version":1,"result":"failure","message":"…"}, optional mit "outcome" für on_result.
  • Events: Zeilen @@ADISPATCH_EVENT {"type":"status","message":"…"} erscheinen live in der UI.

workflows/coder.yaml

id: coder
priority: 50                         # workflow priority comes before item priority
max_concurrent: 2
identity: coder                      # GitHub App used for labels, fields and comments
trigger: { type: source, source: app }   # or { type: schedule, cron: '0 3 * * *' } / { type: manual }
filter:
  all:
    - { field: labels, operator: contains, value: ready-for-coding }
    - { field: project.Status, operator: in, value: [Todo, Ready] }
    - { field: dependencies.blocked_by_open, operator: equals, value: 0 }
item_priority:
  rules:
    - when: { field: project.Priority, operator: in, value: [P0, P1] }
      value: 100
conditions:
  workflow: [ { type: shell, command: scripts/check-credits.sh, cache: 60s } ]
execute:
  type: shell
  command: scripts/coder.sh          # relative to the config dir; no shell, args as a list
  args: ['{item.url}']
  secrets:
    GH_TOKEN: { identity: coder, delivery: file }   # renewed token file in $GH_TOKEN_FILE
  timeout: { max_duration: 6h, idle_timeout: 45m }
  keep: ['.agent/transcript.jsonl']  # saved to runs/<run-id>/kept/ before cleanup
retry: { max_technical_failures: 3 }
on_start:
  - type: source.update
    labels: { add: [coding], remove: [ready-for-coding] }
    project: { Status: In Progress }
on_success:
  - type: source.update
    labels: { add: [ready-for-review], remove: [coding] }
    project: { Status: Review }
  - type: source.comment
    message: 'Done (run {run.id}): {result.message}'
on_failure:
  - type: source.update
    labels: { add: [needs-maintainer], remove: [coding] }
on_result:                           # named outcome from the result file
  needs_split:
    - type: source.comment
      message: 'Too big, please split: {result.message}'
    - type: source.update
      labels: { add: [needs-maintainer], remove: [coding] }
on_error:                            # crashed, start_error, timed_out, cancelled
  - type: source.update
    labels: { add: [ready-for-coding], remove: [coding] }
    project: { Status: Ready }

Quickstart

Linux or macOS with Node.js ≥ 22.13 (24 LTS recommended). No GitHub account needed for the first run.

Linux oder macOS mit Node.js ≥ 22.13 (empfohlen 24 LTS). Für den ersten Run ist kein GitHub-Konto nötig.

  1. InstallInstallieren

    npm install -g adispatchd        # or try it: npx adispatchd --help
  2. Create a sample configurationBeispielkonfiguration anlegen

    Never overwrites existing files and validates the result.

    Überschreibt nie bestehende Dateien und validiert das Ergebnis.

    adispatch init --config-dir ./config --data-dir ./data
  3. Start the daemonDaemon starten

    Runs in the foreground; Ctrl-C drains active runs.

    Läuft im Vordergrund; Ctrl-C lässt aktive Runs geordnet auslaufen (Drain).

    adispatch start --config-dir ./config --data-dir ./data
  4. Open the web UIWeboberfläche öffnen

    Go to http://127.0.0.1:8787/ and start the hello workflow under Workflows – or via the API:

    http://127.0.0.1:8787/ öffnen und unter Workflows den Workflow hello starten – oder per API:

    curl -X POST -H 'X-Adispatch-Request: 1' -H 'Content-Type: application/json' \
         -d '{"parameters":{"name":"Ada"}}' http://127.0.0.1:8787/api/v1/workflows/hello/start
  5. Connect GitHubGitHub anbinden

    Create a GitHub App per role, put its IDs into identities.yaml and the repository into sources.yaml, export the private key, set enabled: true in workflows/coder.yaml and run adispatch config reload --config-dir ./config.

    GitHub App je Rolle anlegen, IDs in identities.yaml und das Repository in sources.yaml eintragen, Private Key exportieren, in workflows/coder.yaml enabled: true setzen und adispatch config reload --config-dir ./config ausführen.

Security in briefSicherheit in Kürze

  • Optional login. auth.mode: password (built-in users, scrypt hashes) or trusted_header (Tailscale Serve, oauth2-proxy) with the roles viewer, operator and admin; without it the API binds to 127.0.0.1 by default – for remote access use an authenticating reverse proxy, VPN or Tailscale and set server.allowed_hosts.
  • Scripts and YAML are trusted code running with the service's permissions – there is no sandbox.
  • No isolation between runs of the same service user. Separate high- and low-privilege workflows into separate instances, users or containers.
  • Workers only receive explicitly declared secrets; private keys come from environment variables, never from YAML.
  • Anmeldung optional. auth.mode: password (eingebaute Benutzer, scrypt-Hashes) oder trusted_header (Tailscale Serve, oauth2-proxy) mit den Rollen viewer, operator und admin; ohne Anmeldung bindet die API standardmäßig an 127.0.0.1 – Fernzugriff dann nur über einen authentifizierenden Reverse Proxy, VPN oder Tailscale mit server.allowed_hosts.
  • Skripte und YAML sind vertrauenswürdiger Code mit den Rechten des Dienstes – keine Sandbox.
  • Keine Isolation zwischen Runs desselben Dienstbenutzers. Hoch- und niedrig-privilegierte Workflows in getrennten Instanzen, Benutzern oder Containern betreiben.
  • Worker erhalten nur ausdrücklich deklarierte Secrets; Private Keys kommen aus Umgebungsvariablen, nie aus YAML.