Skip to content

fleet step 4: dispatch thin client domain over pm-stack #5432

Closed

Description

@developing-today

Part of epic #5412. Design of record: #5425 (§6, §7 step 4 — "Dispatch (thin)"). Predecessor: step 3 (proxy domains). Gate: the thin verbs round-trip against the live pm-stack interfaces with pm-stack itself untouched.

Scope

  • fleet-dispatch: a thin client domain over pm-stack dispatch's existing interfaces (D6). "Dispatch" today is the pm-stack TypeScript/Bun service suite in this repo (its own systemd units, SOPS-rendered state) plus the hatch-personal steward cron gating it — it is not CLI-shaped, and wholesale absorption would be a rewrite. This step adds client verbs only.
  • pm-stack is not rewritten, rehomed, or re-platformed by this epic. Any interface gap found while building the verbs is filed as its own issue against pm-stack, not absorbed here.
  • The steward cron's call sites move to the fleet verbs only if they want them — no cron is rewritten by this epic (§7 step 4).
  • Dispatch appears in the registry / --help / capabilities surface like every domain, and is absent where the §2 matrix says so (fleet-connect stubs it at 69; fleet-serve carries no dispatch client surface).

Gate

  • The agreed verb set round-trips against the live dispatch interfaces from the full fleet build, with evidence on this issue.
  • pm-stack diff attributable to this step: zero.

Acceptance criteria

  • dispatch thin-client domain runs over pm-stack as described.
  • No regression: existing tests/checks for the touched area remain green, and the change lands via PR referencing this issue.
  • Evidence (PR link, test/CI output, or verification note) is posted on this issue before it is closed.

Activity

  1. dezren39 commented on Nov 4, 2026

    @dezren39
    Member

    Intake review

    🤖 Agent note · state: filed

    • Agent: fleet monitor
    • Session / ID: repo-fleet-monitor run 2026-11-04 06:12 CST
    • Found via: monitor reconciliation
    • Task: Intake enrichment (protocol §5): verified still live (no merged PR or closed duplicate found naming this fix in a bounded check this run), appended acceptance criteria, normalized labels.
    • Expectation: as labelled (effort class on the issue); done = acceptance criteria met with evidence linked here.
    • Links: this issue

    Related: epic #5412 and steps #5429-#5433 form one set; #5413 is a sibling absorption task.
    Notes: Newly filed this morning with no priority label and no acceptance-criteria section; both now supplied.
    Risks: Sequencing: steps are gated in order (notably #5433 on the #5350 step 6 cutover); starting out of order risks rework.
    Suggested approach: Triage through the queue in dependency order, one step per task, verifying each step's acceptance evidence before closing.

  2. added
    meta/enrichedIntake review done: acceptance criteria, links, and normalized labels added
    on Nov 4, 2026
  3. added 3 commits that reference this issue on Nov 4, 2026
  4. developing-today commented on Nov 4, 2026

    @developing-today
    ContributorAuthor

    Step 4 complete. PR #5551 merged as 7648547062484404f5c8085b4b6cd0a808b76ed3 (read-back verified: PR state MERGED, commits/main returned the same SHA). The dispatch thin client is live in the full fleet build; pm-stack diff attributable to this step: zero.

    What shipped

    New crate crates/fleet-dispatch, wired into the bin's existing dispatch feature and the registry entry (which gains the enqueue verb the live API carries):

    • fleet dispatch status → GET /health
    • fleet dispatch launch … → POST /v1/sessions
    • fleet dispatch enqueue … → POST /v1/tasks (kind validated client-side against the service's TASK_KINDS)

    The API surface was discovered from the code, not assumed: pm-stack/dispatch/src/{server,auth,config,validate,packet,queue}.ts + the dispatch README. Auth is the service's static bearer, resolved by name through fleet-secrets (pm-dispatch.api-token; [dispatch] token_name overrides) and sent as the one Authorization header — never printed, logged, or stored. Transports follow fleet-queue's two-shape pattern: direct http:// (FLEET_DISPATCH_URL / [dispatch] url in the new fleet-config [dispatch] table, or the laptop seat's loopback probe), otherwise a CONNECT tunnel on the proxy egress via fleet-proxy's tunnel module. Exit codes: 0 ok · 2 usage · 3 transport/credentials · 4 service HTTP error · 69 stub machinery (fleet-serve and fleet-connect stub dispatch, asserted in smoke).

    Live round-trips (the shipped builds, against the running service, version d94b895471024a21bf88358a4f451dc76df5aa7f)

    Laptop (full build, direct loopback):

    • dispatch status → {"ok": true, "status": "ok", "version": "d94b8954…", "transport": "direct http://127.0.0.1:4879"}, exit 0
    • dispatch enqueue (dedupe key fleet-step4-proof-5432) → 201 {"task_id": 9645, "issue_number": null, "queue": "repo-health-code-pilot"}
    • same enqueue again → the same task back with "pre_existing": true and the service's dedupe warning (no error, no duplicate)
    • dispatch launch --no-wait → 202 {"task_id": 9647, "issue_number": 5548, "queue": "repo-health-code-pilot", "status": "launching"}; the task was claimed by dispatch-opencode, issue fleet step 4 thin-client proof (launch) #5548 got queue/claimed + work/assigned, and session ses_e5e58dfa050ffexldsrSut6fjd replied with the requested proof marker and made no changes

    Sandbox (fleet-static build, tunnel transport):

    • dispatch status → same health body, "transport": "tunnel via proxy egress to 100.99.113.103:4879"
    • dispatch enqueue (dedupe key fleet-step4-proof-5432-sandbox) → 201 {"task_id": 9648, …}

    Cleanup verified in the queue store: 9645 cancelled, 9647 done (completed under its claim holder), 9648 cancelled. Proof issue #5548 commented and closed. Post-merge, the sandbox ~/tools/fleet converged to the merged build (sha256 1f515fbb93ea17c9e6c95159363106ddf1c361663403c4a80bff6cfc5b28616f); dispatch status and repo-queue health re-proven through it.

    Gates

    • cargo test --workspace: 51 passed, 0 failed (fleet-dispatch +6, fleet-config +2 new); cargo fmt --check clean; clippy clean (with the rustup 1.99.0 toolchain first in PATH — the laptop's mixed cargo/clippy provenance otherwise raises E0514 on pre-existing crates identically).
    • All six nix packages build (fleet, fleet-serve, fleet-connect, fleet-connect-static, fleet-static, fleet-brokers).
    • Smoke: 62 passed, 0 failed, including the new hermetic dispatch section (stub pm-dispatch + stub bws on PATH): status round-trip + transport naming, launch and enqueue round-trips with the by-name bearer, a service 409 → exit 4 with its message, an unknown kind → exit 2, an unresolvable token name → exit 3 before any request. One build-system note: the workspace member list is explicit — crates/fleet-dispatch is registered there, and the new crate's files must be git-staged for flake builds to see them (untracked files are outside the flake source copy).

    Deltas: live API vs the design note's assumptions

    1. No rendered state dir. The note's ~/.local/state/pm-dispatch/ does not exist; pm-dispatch keeps no client-readable state files. Per-task status therefore rides the queue domain (fleet queue list), and the client is endpoints-only.
    2. Verb set. The note named status + launch; the live API's submission surface also includes POST /v1/tasks, so the shipped set is status / launch / enqueue.
    3. Auth. Of the service's two forms, GitHub Actions OIDC is not mintable by a local client; the static bearer is the client form. The framed session-broker transport cannot carry it (it forwards only X-Agent-Token), which is why the sandbox route is a direct CONNECT tunnel on the same egress rather than a framed proxy request.
    4. The token had no by-name home. DISPATCH_API_TOKEN lived only in the laptop's ~/secrets/dispatch.env; fleet-secrets had nothing to resolve. Filed in Bitwarden as pm-dispatch.api-token (same project as the other pm-stack secrets; value verified byte-identical to the service's by sha256, never printed). No pm-stack code or config changed.
    5. Dedupe is a success shape, not an error: a consumed dedupe_key returns the pre-existing task with pre_existing: true + a warning.

    Interface gaps filed against pm-stack as their own issue, per this step's scope rule: #5555 (no read/status endpoint for launched/enqueued work; the vault-home documentation point). The steward cron's call sites were not touched — they may adopt these verbs on their own schedule, as the design specifies.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area/infraInfrastructure, deployment, and operationseffort/mMedium: expected under 1 day of workkind/enhancementmeta/enrichedIntake review done: acceptance criteria, links, and normalized labels addedpriority/normalOrdinary priority (the default)status/needs-triageNot yet triaged; needs a first look

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions