Dispatch is a self-hosted Next.js/TypeScript Kanban and work dispatch layer for AI agents. It uses Prisma with PostgreSQL as its database.
- Framework: Next.js 16 (App Router)
- Language: TypeScript
- Database: PostgreSQL via Prisma v7 ORM
- Styling: Tailwind CSS
- Container: Docker (Debian bookworm-slim based)
mise run bootstrap # Pinned Node + deps + Prisma client (idempotent; no DATABASE_URL, no migrations, no git hooks)
mise run check # Lint + typecheck (mirrors CI jobs)
mise run test # Vitest suite
npm install # Install dependencies (runs `prisma generate` via postinstall)
npm run dev # Start development server
npm run build # Production build
npm run lint # Lint check
npm run typecheck # TypeScript check
npm run db:generate # Regenerate Prisma client (also runs on postinstall; re-run after editing schema.prisma)
npm run db:push # Push schema (dev)
npm run db:deploy # Deploy migrations (prod).mise/config.toml + .mise/mise.lock are the repository development-bootstrap contract
(Courier #414): Node is the pinned runtime tool, bootstrap/check/test are the shared task
names, and check/test delegate to the authoritative npm run commands. Do not add
git-hook installation, migrations, DATABASE_URL requirements, or test execution to bootstrap.
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | PostgreSQL connection string (canonical) |
GITHUB_TOKEN |
Yes | GitHub Personal Access Token |
DISPATCH_AGENT_TOKEN |
Yes | Bearer token for agent API (maintainer tier — full rights) |
DISPATCH_MAINTAINER_TOKEN |
No | Optional alias for the maintainer-tier bearer token (same rights as DISPATCH_AGENT_TOKEN) |
DISPATCH_WORKER_TOKEN |
No | Legacy unbound worker-tier bearer token. Explicit allowlist: next-task, tasks/report, heartbeat, active-work, queue, work-summary, agent-work start/checkpoint/finish + GET listing, non-force claim / unclaim, issue state/status, PR-fix queue reads, PR-fix hand-out ack. It carries no agent identity: identity-scoped routes (all /api/agents/{agentName}/*, agent-work start/checkpoint/finish + GET, claim/unclaim, status, PR-fix queue marks — a settlement must hold the row's hand-out, #1428) now require a bound credential from DISPATCH_WORKER_TOKENS and return 403 for this token; it remains a migration-window fallback for identity-free routes (issue listing, PR-fix reads) and logs a deprecation warning at boot. A value duplicating a maintainer token resolves to worker tier (fail-closed). |
DISPATCH_WORKER_TOKENS |
No | Bound worker credentials, comma/newline-separated agentName:token pairs. Each token authenticates as exactly that agent; the caller's identity is derived from the token, never from x-agent-name or a request-body agentName. A bound worker may only read/report/poll/start/checkpoint/finish/claim/unclaim/set-status for its own agent (status is additionally scoped to issues assigned to it); a token bound to two different agents is rejected (fail-closed). Multiple tokens per agent support rotation. Agent-work operator release/reassign and sweep stay maintainer-only. |
GITHUB_REPOSITORIES |
Yes | One-time bootstrap seed for tracked repos (comma or newline separated). Read only when AutomationRepo is empty. After first seed, manage via /automation UI or POST /api/automation/repos. Seeded repos carry source: "env"; UI-added repos carry source: "user". |
DISPATCH_URL |
No | Base URL of your Dispatch instance (used by outbound clients and MCP bridge) |
DISPATCH_DATABASE_URL |
No | Alternative database URL alias — used if DATABASE_URL is not set |
NEXTAUTH_SECRET |
No | NextAuth.js secret |
NEXTAUTH_URL |
No | NextAuth.js URL |
DISPATCH_SCHEDULER_ENABLED |
No | true runs periodic jobs (sync, groomer, PR-followup, prune-closed) in-process instead of via external cronjobs. Off by default. Confine to a single replica. |
DISPATCH_SYNC_INTERVAL_MS |
No | Scheduled-sync interval when the scheduler is enabled (default 900000 = 15m; 0 disables the job) |
DISPATCH_SYNC_PIPELINE |
No | Declares whether the issue-sync pipeline is expected to run: auto (default — "the in-process scheduler decides"), external (required for a cron/CronJob-driven pipeline, so the sync-freshness alerts fire without the in-process scheduler), or off (explicit operator opt-out, never a page). The arm state is the declaration plus in-process facts; sync history is never evidence. See docs/observability-packaging.md. |
DISPATCH_GROOMER_INTERVAL_MS |
No | Groomer run interval (default 600000 = 10m; 0 disables) |
DISPATCH_GROOMER_MODE |
No | Off/observe/auto rollout mode for the hosted groomer (#1542). Unset maps from DISPATCH_GROOMER_DRY_RUN (true→observe, false→auto); an explicit mode wins over the dry-run boolean. observe runs the full assessment with zero external writes and records a durable would_act/would_noop/would_refuse/assessment_unavailable disposition on GroomingRun; see docs/hosted-groomer.md. |
DISPATCH_PR_FOLLOWUP_INTERVAL_MS |
No | PR-followup sync interval (default 900000 = 15m; 0 disables) |
DISPATCH_PRUNE_CLOSED_INTERVAL_MS |
No | Closed-issue prune interval (default 86400000 = 24h; 0 disables) |
DISPATCH_AGENT_EXECUTIONS_PRUNE_INTERVAL_MS |
No | Terminal AgentExecution retention-prune interval (#1499, default 86400000 = 24h; 0 disables). The retention window itself is DISPATCH_AGENT_EXECUTION_RETENTION_MS (default 7d, clamped 1h–90d); the prune deletes terminal observation rows older than it and never touches AgentWork, Lease, or ownership state. |
DISPATCH_CAPACITY_COOLDOWN_MS |
No | Bounded cooldown for explicit capacity-exhaustion handoffs (default 900000 = 15m, clamped 1m–6h). Scoped to the exhausted capacity on the handed item only; unrelated capacities keep dispatch. See docs/capacity-handoff.md |
DISPATCH_AGENT_ACTIVITY_WINDOW_MS |
No | Recency window for the Agents page default view (#1494): an identity with no open assignment and no live work stays visible while its latest AgentRun report/heartbeat falls inside it (default 604800000 = 7d, clamped 1m–30d). Hiding is display-only; history is never deleted and new activity re-lists the identity. See src/lib/agent-directory.ts. |
DISPATCH_OWNERSHIP_ACTIVE_WINDOW_MS |
No | Ownership liveness window (#1251): renewals inside it keep a claim CLAIMED_ACTIVE (default 900000 = 15m, clamped 1m–6h). See docs/worker-ownership-capability.md |
DISPATCH_OWNERSHIP_LONG_RUN_GRACE_MS |
No | Long-run grace (#1251): sustained silence inside it is CLAIMED_LONG_RUNNING, never release (default 2700000 = 45m, clamped ≥ active window, ≤24h) |
DISPATCH_OWNERSHIP_RENEW_TTL_MS |
No | Expiry a renewed lease receives (#1251) (default 1800000 = 30m, clamped 1m–24h) |
DISPATCH_OWNERSHIP_RECONCILE_MODE |
No | Ownership reconciler (#1254): off (default — starts no new release, but still settles intents committed before the mode change), audit (annotate ladder candidates only — zero forge writes, zero release mutations, no replay either) or enforce (release confirmed-abandoned claims through the §8.7 two-sided intent protocol). Runs in the stale-work scheduler slot. |
DISPATCH_RECONCILE_MAX_RECLAIMS |
No | Per-claim reclaim counter cap (#1254); on exhaustion the claim parks as needs-human instead of churning (default 3, clamped 1–10). |
DISPATCH_RECONCILE_BATCH_SIZE |
No | Candidates examined per reconcile pass (#1254 per-run quota) (default 25, clamped 1–200). |
DISPATCH_RECONCILE_RECHECK_INTERVAL_MS |
No | Separation between the two confirmation observations of the ladder (#1254 §8.2) (default 300000 = 5m, clamped 1m–24h). |
DISPATCH_RECONCILE_RECLAIM_COOLDOWN_MS |
No | Minimum spacing between release attempts on the same claim (#1254 §8.3) (default = the re-check interval, clamped 0–24h). |
DISPATCH_RECONCILE_MAX_FORGE_RETRIES |
No | Bounded Phase-B forge retries before a pending release intent parks for the operator (#1254 §8.7 fail-closed) (default 5, clamped 1–20). |
DISPATCH_RECONCILE_ANNOTATION_REEMIT_MS |
No | Re-emit window for the reconciler's per-pass audit annotations (#1363): a per-candidate annotation identical to the candidate's latest one is suppressed until the window elapses, then re-emitted once as a fresh confirmation; state, ladder and label changes are always annotated (default 86400000 = 24h, clamped 1m–7d). |
DISPATCH_GITHUB_TIMEOUT_MS |
No | Hard per-attempt timeout on every GitHub REST request (#1347), plus a capped retry backoff — the enforced premise of the guard-override silence fence and the reconciler's commit-time re-checks (default 30000, floored at 1000). |
DISPATCH_REBASE_AGENTS |
No | Comma or whitespace separated agent names (case-insensitive) eligible for REBASE lane hand-outs (#1327) — the operator-declared local-only worker pool. An agent outside the pool defers the candidate (item stays QUEUED for an eligible poller). FAIL CLOSED by default: unset or empty admits no agent and the lane stops dispatching until the operator names workers; a bare * value is the explicit unrestricted opt-in (debugging/backcompat). A REBASE hand-out also needs the polling client to declare the rebase-executor:1 capability in that same request (#1532): the pool names agents, and an allowlisted name alone does not prove the client can execute a mechanical rebase. A poll without the declaration defers the candidate and the item stays QUEUED for a capable poll. See docs/rebase-only-intake.md. |
DISPATCH_QUEUE_ADMISSION_MODE |
No | Grooming freshness admission for worker pickup: off (default, unchanged), audit (annotate queue items, filter nothing) or enforce (withhold ready issues without a current applied grooming decision or operator override). See docs/hosted-groomer.md. |
DISPATCH_NO_PR_CONCLUSION_MODE |
No | Terminal no-PR conclusion park (#1506): an implement report of no_changes_needed / already_addressed / already_done parks the issue out of queue pickup. A report that carries a PR-fix attempt token is PR-fix work and parks nothing. A worker-tier report may only park an issue its agent actually owns (live agent/<agent> label check); other reports still succeed but park nothing. enforce (default, withhold), audit (annotate only), off (legacy). The park is a Dispatch-side ledger row (no GitHub writes); it ends on an issue title/body edit or an operator clear (DELETE /api/issues/[issueId]/no-pr-conclusion, status set, board move, maintainer force-claim). Unrecognized values run audit. See docs/no-pr-conclusion-park.md. |
DISPATCH_BLOCKED_EXTERNAL_PARK_THRESHOLD |
No | Repeated blocked-settlement park (#1562): this many CONSECUTIVE blocked / waiting-external implement settlements carrying the same missing-prerequisite descriptor park an issue out of queue pickup. A different descriptor resets the count, so the first settlement always stays recoverable and re-offerable. Integer clamped 2–10, default 3. See docs/blocked-external-park.md. |
DISPATCH_BLOCKED_EXTERNAL_PARK_MODE |
No | Rollout mode for the #1562 park: enforce (default, withhold), audit (annotate queue items only), off (nothing withheld; the ledger still records settlements, so a later switch to enforce reflects the history the operator already saw). The ledger row is Dispatch-side only (no GitHub writes); it ends on an issue title/body edit, an operator clear (DELETE /api/issues/[issueId]/blocked-external, status set, board move, maintainer force-claim), or a later implement report that shows real progress or a terminal conclusion (not a failed/handed-off/capacity release). Unrecognized values run audit. See docs/blocked-external-park.md. |
DISPATCH_OBSERVABILITY_STRANDED_ASSIGNMENT_MS |
No | Age threshold for the read-only GET /api/observability snapshot (#1252): known-age agent/* assignments with no observed Lease/AgentWork execution past this age count as aged un-picked-up (default 1800000 = 30m, clamped 1m–24h). Assignments without persisted age evidence stay unknown and are never reported aged. See docs/observability-snapshot.md. |
DISPATCH_GROOMING_STALL_INTERVALS |
No | Groomer stall window for the A14 starvation signal (#1420): N groomer intervals (DISPATCH_GROOMER_INTERVAL_MS) without a successful applied grooming run, while DISPATCH_QUEUE_ADMISSION_MODE=enforce withholds ready work, arms the alert (default 2, clamped 1–24). The stall never bypasses enforcement; it names the outage and the impacted-ready count. See docs/observability.md §4.8/§6. |
DISPATCH_METRICS_ENABLED |
No | Opt-in Prometheus text exposition of the observability catalogue at GET /api/metrics (#1255): strict === "true", default off (404 while disabled). Read-only, maintainer tier only. See docs/observability-packaging.md. |
DISPATCH_COURIER_ADMISSION_URL |
No | Operator-configured Courier HTTP admission endpoint (#1537, e.g. https://courier.internal/v1/contributions; Courier #708 wire contract). http(s) URLs only. Never derived from request data; fetch refuses redirects, so a credential-bearing request cannot be bounced elsewhere. See docs/external-contributions.md. |
DISPATCH_COURIER_ADMISSION_TOKEN |
No | Server-side bearer credential for the admission endpoint (#1537). Never logged, stored, or returned; every diagnostic is token-redacted before it reaches a log, the ledger, or a response. |
DISPATCH_COURIER_ADMISSION_NAMESPACES |
No | Approved Courier namespaces for submission (#1537), comma/newline separated. Fail-closed: unset or empty disables submission; a request outside the list is a 400. |
DISPATCH_COURIER_ADMISSION_TIMEOUT_MS |
No | Per-attempt timeout on the admission POST (#1537), floored at 1s (default 30000). A timeout is uncertain delivery, retried later with the original idempotency key. |
Resolution order: DATABASE_URL > DISPATCH_DATABASE_URL. DISPATCH_AGENT_TOKEN for agent API bearer auth.
Labels follow a category/value pattern:
- Status:
status/backlog,status/ready,status/in-progress,status/in-review,status/blocked,status/done - Owner:
owner/*(e.g.,owner/alice); Board owner filtering is label-based and does not use GitHub assignees - Agent:
agent/*(e.g.,agent/alpha); Board agent filtering is label-based and does not use AgentRun names, configured agents, or GitHub assignees - Project:
project/*(e.g.,project/k8s) - Note:
project/*labels are optional and not required for the Projects view. Projects groups issues by repository by default. - Priority:
priority/p0throughpriority/p3 - Type:
type/bug,type/feature,type/chore,type/research,type/security
.github/labels.yaml is the source of truth for managed labels. The label-sync
workflow (.github/workflows/label-sync.yaml) keeps the repo in sync with it.
- Umbrella labels are allowed; per-attempt / per-model variants are not.
blocked/infrais the single managed label for infrastructure-blocked work. Do NOT createblocked/infra-attempt/<N>orblocked/infra-model/<name>labels: an external emitter (bridge / pr-followup path) used to write a fresh one per failed GHA attempt, accumulating ~95 dead labels that polluted every kanban label filter, repo add-label call, and audit view (#916). - The label-sync workflow prunes exactly the
blocked/infra-attempt/*andblocked/infra-model/*shapes (on push to main touching the label files and on a weekly schedule) and keeps theblocked/infraumbrella. - If you need to distinguish infra failure modes, encode them in the issue body / audit log, not in a new label. A single repo failure mode does not need N unique identifiers.
Dispatch classifies issues into three execution lanes:
- NORMAL: Concrete, scoped, testable implementation work suitable for a standard worker. Examples: bounded frontend/backend fixes, documentation, tests, CI/lint, release/version drift, dependency updates, concrete follow-up issues with clear acceptance criteria.
- ESCALATED: Requires higher-judgment model support (may be GPT-5.5, Claude Opus, GLM-5.1, or another provider). Examples: architecture/security/API/auth boundary design, database/schema migration strategy, distributed/cross-service design, ambiguous product behavior, broad refactor planning, RFC/design/alternatives decisions, audit parent decomposition.
- BACKLOG: Not actionable yet — placeholder, missing enough detail, or a parent/umbrella item that hasn't been decomposed into concrete work.
Each classification stores: lane, confidence (high/medium/low), reason, and model/source. A full history of classifications is maintained in the IssueLane table.
Routing rules:
- Do NOT route to ESCALATED only because labels include
needs-escalation, legacyneeds-gpt,escalated, orpriority/p1. - DO route broad audit parent/umbrella issues to ESCALATED for decomposition/design unless already decomposed.
- If the issue has clear acceptance criteria, prefer NORMAL.
- If confidence is low and the issue is not actionable, choose BACKLOG.
API endpoints:
GET /api/issues/[issueId]/lane— get the current lane classification for an issuePOST /api/issues/[issueId]/lane— classify or reclassify an issue (body:{ force?: boolean, model?: string, classification?: Record<string, unknown> }). Omit body to return current lane; provideclassificationto set it directly; useforceto bypass lazy evaluation.
Agent queue integration:
- Agent queue endpoint (
GET /api/agents/[agentName]/queue) accepts alanequery param to filter by lane. - By default, BACKLOG issues are excluded from the normal agent queue.
- Grooming admission (
DISPATCH_QUEUE_ADMISSION_MODE, #1065): inaudit/enforceeach issue item carries anadmissiondecision with machine-readable reason codes; inenforcewithheld ready issues are left out (?includeWithheld=truelists them withclaimable: false). PR-fix items are never gated. Operators admit an issue explicitly withPOST /api/issues/[issueId]/admission-override(DELETEclears it); operator auth only (OIDC, basic, or auth disabled), agent bearer tokens get 403. A barestatus/readylabel is not an override.
Sync integration:
- The sync pipeline can optionally classify lanes after syncing issues. Classification failures default to NORMAL and do not break the sync.
For read-only automation visibility:
metadata:read,contents:read,actions:read,pull_requests:read,packages:read
For control actions (rerun, dispatch):
actions:write
- No agent-specific names in generic docs - Use generic patterns like
agent/*not specific agent names - Prisma schema - Keep relations strict; do not make foreign keys nullable to hide bugs
- API routes - Return appropriate HTTP status codes; use JSON for responses
- Error handling - Use
error instanceof Errorpattern; provide meaningful error messages - Validation - Validate inputs before database operations
- No commit of secrets - Never commit
.envfiles,node_modules,.next, or build output
- Schema is in
prisma/schema.prisma - Local dev:
db push(push schema without migrations) - Production:
prisma migrate deployruns automatically on container startup - binaryTargets includes
linux-arm64-openssl-3.0.xfor Debian bookworm-slim runtime
- Base image:
node:26-bookworm-slim - OpenSSL and ca-certificates installed in builder stage for Prisma generation
- Runtime image installs same packages for Prisma client operation
- Multi-stage build: deps → builder → runner
.github/workflows/image.yamlbuilds and publishes to GHCR- Trivy scanning is advisory only (
continue-on-error: true) - Lint/typecheck blocks CI; must pass
- Push to
maincreatesmainandsha-<shortsha>tags
- Ensure
npx prisma generatehas run during build - Check OpenSSL is installed in the image
- Verify correct binaryTargets for the runtime
- Next.js static generation runs at build time; Prisma initializes lazily
- The error appears during build but does not block it; runtime needs the env var
src/
app/
api/
agents/[agentName]/ # next-task, tasks/report, queue, heartbeat, active-work
agent-work/ # start, checkpoint, finish, operator release
agent-runs/ # legacy run ingestion
audit/ # audit log read
automation/ # sync, runs, workflows, events, repos
health/ # health check
issues/ # listing, move, claim, unclaim, unassign, status, groom, lane, actions, reconcile, refresh, prune-closed, untriaged, webhook
observability/ # read-only standalone signal snapshot (#1252)
pr-fix-queue/ # enqueue, queued, mark
pr-followup/ # sync, webhook
repos/ # repository config
sync/ # issue sync (+ scheduled)
automation/ # automation UI
board/ # Kanban board (page.tsx server, kanban-board-client.tsx client)
projects/ # project view
agents/ # agent activity page
components/
kanban-board.tsx # board DnD context, auto-refresh, move logic
issue-card.tsx # card + ⋮ assignment/groom dropdown
kanban-column.tsx # droppable column
filter-bar.tsx # board filters
lib/
prisma.ts # Prisma client singleton
github.ts # GitHub label/comment API helpers
auth.ts # authorizeRequest, getAuthorizedActor (bearer/basic/oidc)
lease.ts # Lease TTL, upsert, release, resolveActiveWork
issue-lifecycle.ts # IssueLifecycleEvent taxonomy + idempotent recorder
agent-work.ts # AgentWork start/checkpoint/finish, stale recovery
assignment-conflicts.ts # label conflict analysis (agent/owner add/remove)
kanban.ts # getIssuesByStatus, getIssueStatus
client-auth.ts # authedFetch (browser → API, session-aware)
version.ts # version label for header
types/index.ts # Issue, StatusLabel, BOARD_COLUMNS, label helpers
Cross-cutting concerns and where they live. Read these first when working in that area.
| Concern | Files |
|---|---|
| Issue status movement (board drag) | components/kanban-board.tsx (client move), app/api/issues/move/route.ts |
| Agent claim / unclaim (lease + status) | app/api/issues/claim/route.ts, app/api/issues/unclaim/route.ts, lib/lease.ts |
| Assign / unassign (label-only, no lease) | app/api/issues/actions/route.ts, app/api/issues/unassign/route.ts, lib/assignment-conflicts.ts |
| AgentWork lifecycle | app/api/agent-work/route.ts, app/api/agent-work/{start,checkpoint,finish}/route.ts, lib/agent-work.ts |
| Fenced capacity handoff (#1256) | app/api/issues/capacity-handoff/route.ts, lib/issue-capacity-handoff.ts, lib/capacity-handoff.ts, lib/issue-handoff-settlement.ts (durable replay, #1262), lib/pr-fix-queue.ts (report resolver), lib/lease.ts (claim token), docs/capacity-handoff.md |
| Terminal no-PR conclusion park (#1506) | lib/no-pr-conclusion.ts (fingerprint + ledger + modes), app/api/agents/[agentName]/tasks/report/route.ts (park on terminal implement reports), lib/agent-queue-fetch.ts (withhold filter), app/api/issues/claim/route.ts (409 gate + force discharge), app/api/issues/status/route.ts + app/api/issues/move/route.ts (operator clear), app/api/issues/[issueId]/no-pr-conclusion/ (inspect/clear), docs/no-pr-conclusion-park.md |
| Repeated blocked-settlement park (#1562) | lib/blocked-external-park.ts (descriptor + counter + ledger + modes), app/api/agents/[agentName]/tasks/report/route.ts (count + park on blocked implement reports, clear on fresh outcomes), lib/agent-queue-fetch.ts (withhold filter), app/api/issues/claim/route.ts (409 gate + force discharge), app/api/issues/status/route.ts + app/api/issues/move/route.ts (operator clear), app/api/issues/[issueId]/blocked-external/ (inspect/clear), docs/blocked-external-park.md |
| External contribution requests (#1537) | lib/courier-admission.ts (operator config + credential-bounded HTTP client), lib/external-contribution.ts (persist-before-network ledger + exactly-once state machine), app/api/external-contributions/route.ts (maintainer-tier POST), docs/external-contributions.md |
| Worker ownership capability + derived states (#1251) | app/api/agent-work/ownership/route.ts (register/renew/disposition), lib/worker-ownership.ts, lib/ownership-state.ts (derivation), lib/ownership-inspect.ts, app/api/issues/[issueId]/ownership/ + app/api/issues/ownership/ (operator inspect), docs/worker-ownership-capability.md |
| Rebase-only intake contract (#1532) | lib/rebase-intake.ts (scope + capability parsing, acknowledgment), app/api/agents/[agentName]/next-task/route.ts (400 rejections, intake acknowledgment, REBASE capability gate), lib/agent-queue-fetch.ts (rebase-only queue read), lib/agent-task.ts (intake field), lib/pr-fix-queue.ts (worker pool + concurrency-1 admission), docs/rebase-only-intake.md |
| Ownership reconciliation + §8.7 release intents (#1254) | lib/ownership-reconciliation.ts (ladder + Phase A/B/C + supersede), OwnershipReleaseIntent/OwnershipReconcileCandidate (schema), claim-route barrier (409 while intent pending) + lease-before-remote-add, docs/ownership-reconciliation.md |
| Auth & audit actor identity | lib/auth.ts (authorizeRequest, getAuthorizedActor) |
| Sync-pipeline arm state (#1255) | lib/sync-pipeline.ts (declaration + arm-state resolution), lib/observability-signals.ts (derivation), lib/observability-alerts.ts / lib/observability-export.ts (consumers) |
| Lifecycle event stream (claim/status evidence, #1202) | lib/issue-lifecycle.ts, docs/issue-lifecycle-events.md |
| Standalone observability snapshot (#1252) | app/api/observability/route.ts, lib/observability-signals.ts (pure derivation + allowlists), lib/observability-snapshot.ts (read-only collector), docs/observability-snapshot.md |
| Board UI component tree | app/board/page.tsx → kanban-board-client.tsx → components/kanban-board.tsx → components/issue-card.tsx |
| App shell / header / nav | app/layout.tsx |
| Label & status constants | types/index.ts (BOARD_COLUMNS, StatusLabel, AGENT_PREFIX, OWNER_PREFIX) |
GET /api/health returns:
{
"ok": true,
"database": "ok",
"version": "0.1.13"
}Returns 503 if database is unreachable.
The detailed worker API and lifecycle contract lives in .agents/skills/dispatch-worker-contract/SKILL.md. Load it before changing agent queue, auth, task execution, reporting, or worker lifecycle behavior. It is normative for those areas.
GitHub Issues and PRs remain authoritative; Dispatch's Postgres is a cache, not a replacement source of truth. See the worker-contract skill for the full operational rules.
Issues here are picked up by an autonomous coding loop (dispatch → foreman), and two parts of the body feed deterministic reviewer rails. Agents filing issues in this repo must include both.
1. State the ask in one imperative sentence. The reviewer quotes it verbatim to prove it actually read the issue. If it can only paraphrase, its GO is demoted to NO-GO unless the rail below vouches — costing a revision cycle and an escalation review.
2. Name the concrete file paths the fix is expected to touch (backticks are fine). The scope-overlap rail vouches for a diff that touches a named file, and that vouch is what survives a paraphrased ask.
Name only paths you are confident about. An issue that names files the diff does not touch is read as scope drift and also gets the change rejected — so when unsure, name none rather than guessing.