Skip to content

Repository files navigation

foreman-dispatch-bridge

The integration point between misospace/dispatch (GitHub issue assignment layer) and LLMKube Foreman (agentic execution): a CronJob that claims groomed, ready issues from dispatch lane queues and materializes Foreman Workloads for them — then shepherds the failures.

dispatch lanes ──claim──► bridge ──create──► Workload ──► code → [verify] → review
                              │
                              ├─ retry pass: Failed Workloads are deleted and
                              │  recreated (≤ RETRY_MAX_ATTEMPTS), carrying the
                              │  reviewer's NO-GO findings into the retry coder's
                              │  prompt (explicit spec.pipeline + payload.prompt)
                              └─ escalation: exhausted issues are re-laned to
                                 ESCALATION_LANE + unclaimed; the next tick
                                 claims them there with that lane's coder Agent

[verify] is opt-in — controlled by VERIFY_ENABLED, off by default. Enable it to run the Foreman gate; left off, the bridge relies on repository CI. Enabling requires Foreman >= 0.9.9.

Each tick (one CronJob run): reconcile failures first, then claim one ready issue per lane.

Configuration (env)

Env Default Meaning
DISPATCH_URL http://dispatch.llm:3000 dispatch base URL
DISPATCH_AGENT_TOKEN (required) Bearer token for the dispatch API
DISPATCH_AGENT_NAME foreman-coder queue identity (use a dash, not a slash)
DISPATCH_LANES (empty = discover) lanes polled per tick. Unset, the bridge asks Dispatch for its lane topology (GET /api/lanes) and polls every claimable lane, so adding a lane needs no bridge change. Set explicitly to poll a subset; an explicit value always wins. Falls back to local,cloud,frontier only when unset AND the endpoint is unavailable. Concurrent lane fetches are soft-capped at 16 workers per tick (MAX_LANE_WORKERS); lanes beyond the cap are still polled, just with fewer in-flight requests. If you need to watch more than 16 lanes with full concurrency, split into multiple bridge deployments. An over-cap value logs a one-time WARNING (silence with DISPATCH_LANES_WARN=0).
DISPATCH_LANES_WARN 1 log a one-time WARNING when DISPATCH_LANES exceeds the 16-worker cap; set 0 to silence
FOREMAN_NAMESPACE llm namespace for Workloads
GATEPROFILE_MAP (empty) JSON {repo: GateProfile} with "*" wildcard
LANE_CODER_AGENTS (empty) JSON {lane: coderAgentName} with "*" wildcard; wins over BASE_CODER_AGENTS Keys accept a lane id or a Dispatch role name (default/escalation); an exact lane id wins over a role, which wins over *.
BASE_CODER_AGENTS (empty) JSON {language: coderAgentName} with "*" wildcard; routes the base lane's coder by the repo's GATEPROFILE_MAP language
CODER_AGENT_SLOTS {} JSON map {coderAgent: slotCount} capping each coder's in-flight Workloads; "*" wildcard covers unnamed agents. Empty keeps the legacy issue-number split.
ESCALATION_LANE (empty = by role) lane exhausted issues re-lane into. Unset, the bridge resolves the claimable lane whose Dispatch role is escalation, so this does not have to name a deployment-specific lane id. An explicit value always wins. Empty and unresolvable means escalation is off, as before.
RETRY_MAX_ATTEMPTS 3 attempts before escalate/tombstone A coder NO-GO repeated on a second attempt parks the issue instead of spending the rest of the budget: re-running re-derives the same judgement.
PR_FIX_ENABLED (off) enable the PR-fix drain/reconcile loop
PR_FIX_MAX_ATTEMPTS 3 pr-fix attempts before BLOCKED/tombstone
FIX_FIRST_AGENTS (empty) JSON list ["coder"] (or comma-separated names) of agents that only join the issue rotation when their fix lane is idle (no fix work in flight, a free slot). Fixes stay first-priority on an uncontended slot. See issue #134.
GITHUB_TOKEN (empty) used to check a PR's mergeable_state before marking a pr-fix FIXED (unauthenticated if unset)
VERIFY_ENABLED false set to true to add the verify step and run the Foreman gate. Requires Foreman >= 0.9.9 and a verifier Agent; older Foreman versions reject Workloads without the required verifierAgentRef, so a deployment on one of those must set this.
VERDICT_SELF_GO (unset) Comma-separated lanes whose terminal Workload verdict the bridge is allowed to auto-mark selfGo (recommended for use with foreman dispatch run --wait)
MAX_IN_PROGRESS 0 Maximum concurrent in-progress Workloads per lane (0 disables the cap, i.e. unlimited)
PRUNE_COMPLETED_AFTER_HOURS 6 GC age, in hours, after which Completed Workloads are pruned (set to 0 to disable the sweeper)
PRUNE_FAILED_AFTER_HOURS 48 GC age, in hours, after which Failed/Crashed/Cancelled Workloads are pruned (set to 0 to disable the sweeper)
REVISION_CODER_AGENTS {} JSON map {revisionLane: coderAgentName} overriding the agent used when a revision prompt reaches the coder stage Keys accept a lane id or a Dispatch role name (default/escalation); an exact lane id wins over a role, which wins over *.
REPO_CODER_AGENTS {} JSON map {repoFullName: coderAgentName} overriding the agent used when a generic revision prompt reaches the coder stage
PR_FIX_LANE_AGENTS {} JSON map {prFixLane: coderAgentName} overriding the agent used when a PR-fix prompt reaches the coder stage Keys accept a lane id or a Dispatch role name (default/escalation); an exact lane id wins over a role, which wins over *.
DELETE_WORKLOAD_TIMEOUT_S 60 Seconds the bridge waits for the cluster to acknowledge a Workload deletion request before giving up
LOG_FORMAT json Log line format — json (structured, default) or plain (human-readable)
LOG_LEVEL INFO Minimum log level emitted — one of DEBUG, INFO, WARNING, ERROR
INFRA_PROBE_ENABLED true probe models backing infra-parked issues for automatic recovery
INFRA_PROBE_URL http://litellm.llm:4000/v1 OpenAI-compatible base URL used for recovery probes
INFRA_PROBE_API_KEY (empty) optional bearer token for recovery probes

PR-fix retries preserve the pipeline shape set at creation — rebuild_prfix_manifest reuses the existing spec, so toggling this env variable after a PR-fix Workload exists has no effect on its retries. Issue-path retries always pick up the current env value each attempt because they rebuild from scratch via build_workload.

Backward-compat note (parked-for-human comments): every comment the bridge posts for a needs-human issue now carries a stable path: <value> tag in its header — declared-human, exhausted-attempts, exhausted-infra, go-no-pr, or rail-demoted (a review the issueAsk/scope-overlap rail demoted from GO, which re-running cannot fix, #287) — so triage can group issues by cause without opening the Workload (issue #260). The header line is the closest thing to a contract: it is now **Needs a human decision** (path: ) (or the GO-with-no-PR variant with the same tag), so any operator script that matched the bare **Needs a human decision** line exactly should match on the prefix instead. The reason text, Issue:/Workload/branch: lines, and the footer are unchanged.

RBAC

The bridge needs, in FOREMAN_NAMESPACE:

  • workloads.foreman.llmkube.dev: create, get, list, delete
  • agentictasks.foreman.llmkube.dev: get, list (reads a failed Workload's review findings to build feedback-carrying retries)

Development

pip install -r requirements.txt -r requirements-dev.txt
python -m pytest tests/ -q

Pure-logic modules (claim, workload, retry) take injected transports and k8s callables, so the tests run without a cluster or network.

Releases

Tag vX.Y.Z → CI publishes ghcr.io/misospace/foreman-dispatch-bridge:X.Y.Z and creates the GitHub release. Deployed via home-ops (kubernetes/apps/base/llm/dispatch/foreman-dispatch-bridge/), where the full pipeline is documented in the app README.

History

Extracted from joryirving/containers at 0.5.1 (fresh history); versions continue from 0.6.0.

About

Bridge between misospace/dispatch and LLMKube Foreman: claims groomed issues, materializes Workloads, retries with review feedback, escalates exhausted work.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages