Skip to content

Add daemon mode for label dispatch #70

@VincentShipsIt

Description

@VincentShipsIt

PRD: daemon-mode-label-dispatch

Executive Summary

Adopt Symphony's poll-and-dispatch daemon. A long-running shipcode process polls GitHub Issues filtered by label (`shipcode-ready` by default), sorts candidates, and dispatches pipelines until concurrency caps are hit. Lets users feed shipcode work via issue triage instead of clicking Run for each issue. Foundation for headless / EC2 deployment (#60).

Implementation Checklist

  • New `apps/desktop/src/main/daemon.ts` (or split into `apps/cli`) with a poll loop
  • Loop every `pollIntervalMs` calls `gh issue list --label --state open --json number,title,body,state,labels,createdAt`
  • Candidates filtered by: not in `running`, not in `claimed`, not terminal, blocker rule passes
  • Candidates sorted by priority asc (label-derived or null last), createdAt oldest, number asc
  • Dispatch loop reserves slots until `max_concurrent_agents` (and per-state cap when present)

Problem Statement

Shipcode is GUI-only today. Each pipeline starts when the user clicks Run on a kanban card. There is no way to leave shipcode running unattended and have it consume new work as it appears. This blocks autonomous batch operation and overnight runs.

Goals

  • Daemon mode entry point (CLI or settings toggle).
  • Polls GitHub Issues with configurable label every `pollIntervalMs` (default 30000).
  • Dispatches eligible issues under `max_concurrent_agents` plus optional per-state caps (Add per-state concurrency caps #72).
  • Sorts candidates: priority asc → created_at oldest → number asc.
  • Skips: already running, already claimed, terminal state, blocked-by non-terminal blocker.

Non-Goals

User Stories

  • As a solo founder running shipcode overnight, I want to label issues `shipcode-ready` and have shipcode consume them under a concurrency cap, so my queue progresses without my supervision.
    Acceptance:
    • Adding the label to an open issue causes shipcode to start a pipeline within `pollIntervalMs`.
    • With cap 2 and 5 labeled issues, only 2 pipelines run; remaining 3 dispatch as slots free.
    • Closing an issue removes it from candidate set.
    • Adding the blocked-by relationship to a non-terminal blocker prevents dispatch until blocker resolves.

Functional Requirements

  1. New `apps/desktop/src/main/daemon.ts` (or split into `apps/cli`) with a poll loop.
  2. Loop every `pollIntervalMs` calls `gh issue list --label --state open --json number,title,body,state,labels,createdAt`.
  3. Candidates filtered by: not in `running`, not in `claimed`, not terminal, blocker rule passes.
  4. Candidates sorted by priority asc (label-derived or null last), createdAt oldest, number asc.
  5. Dispatch loop reserves slots until `max_concurrent_agents` (and per-state cap when present).
  6. CLI flag or settings toggle starts the daemon; without it, current GUI-driven mode behaves as today.
  7. Daemon participates in the existing IssueGroupScheduler and reconciliation loop (Cancel pipelines on issue state change #68).

Non-Functional Requirements

  • Poll tick must complete in <5s for 100 candidate issues.
  • API rate limit awareness: coalesce queries; back off on 403/429.
  • Daemon must not crash on transient API failure; log and retry next tick.

Success Criteria

  • Mocked GH API: daemon picks up labeled issue within `pollIntervalMs`.
  • Concurrency cap respected with 5 labeled issues and cap 2.
  • Closed issue removed from candidate set on next tick.
  • Blocker rule: issue with non-terminal `blocked-by` linked issue is skipped.
  • Sort order test: ties broken consistently.
  • Manual: tag three test issues; observe two pipelines dispatch; close one and observe third dispatch within poll interval.

Out of Scope

Dependencies

Verification Plan

  • tests: new `apps/desktop/src/main/daemon.test.ts` (or `packages/pipeline/src/daemon.test.ts`) covering poll, dispatch, sort, blocker rule, cap, API failure.
  • manual: start daemon against a test repo with 3 labeled issues and cap 2; verify dispatch behavior end-to-end.

Risks & Open Questions

  • Label name collision with team conventions — ensure ready label is configurable.
  • Priority extraction: GitHub has no first-class priority field — derive from labels (`p1`/`p2`/`p3`) or keep null.
  • Daemon mode under Electron app lifecycle vs standalone Node process — pick one for v1; standalone Node is closer to Symphony's model and unlocks Run ShipCode on EC2 #60.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request
    No fields configured for Feature.

    Projects

    Status

    Deferred

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions