Event-driven multi-org release orchestration for GitHub and Forgejo.
When managing multiple organizations and repositories, relying purely on GitHub Actions for organizational hygiene quickly drains runner capacity and exhausts API rate limits. Beyond that, release automation, auto-merge, and cross-forge mirror verification require custom glue code scattered across repos.
ops-engine is a pure-infrastructure Python framework that captures incoming webhooks from git forges, places them in a bounded async queue, and processes them through configurable handler modules. Zero business logic — all behavior is driven by YAML config.
| Module | Description | Trigger |
|---|---|---|
| Rate Limit Queue | Bounded async queue with backpressure, retry, dead letter, and metrics | Always |
| Triage & Auto-Labeling | Labels PRs and issues based on title keywords | Webhook |
| Stale Cleanup | Marks and closes old issues across all orgs | Cron |
| Dependency Triggers | Fires repository_dispatch on downstream repos when upstream releases |
Webhook |
| Cron Dispatcher | Centralized sequential cron (CodeQL, etc.) | Cron |
| Release Automation | Creates GitHub/Forgejo releases on tag push with CHANGELOG parsing | Webhook |
| Auto-Merge | Merges PRs when CI passes and trigger label is present | Webhook |
| Mirror Verification | Verifies cross-forge mirror sync (Forgejo ↔ GitHub) | Webhook |
| Notifications | Multi-channel alerts (webhook, Slack, Discord) with event filtering | Event |
| Event Deduplication | In-memory webhook dedup for GitHub, Forgejo, and Gitea delivery IDs | Always |
Layer 1: ops-engine (this package — pip install)
QueueManager, ForgeAdapter, Handler Modules, Config Models
Layer 2: xyz-ops (your org layover — private repo per org)
FastAPI app, config.yml, webhook endpoints, cron loop
Layer 3: .ops.yaml (per-repo overrides — optional)
Repo-specific config that overrides org defaults
ops-engine has no dependency on any specific CI system, AI tool, or orchestration framework. The interface is the webhook. A git push --tags produces the same result regardless of what triggered it.
pip install git+https://github.com/LangeVC/ops-engine.gitimport asyncio
from fastapi import FastAPI, Request
from ops_engine import (
QueueManager, QueueMetrics, EventDeduplicator,
TriageHandler, ReleaseHandler, MergeHandler,
OpsEngineConfig,
)
from ops_engine.adapters.github_adapter import GithubAdapter
app = FastAPI()
queue = QueueManager(rate_limit_delay_seconds=1.0, max_queue_size=1000)
dedup = EventDeduplicator()
adapter = GithubAdapter(token="...", webhook_secret="...")
config = OpsEngineConfig(...) # loaded from config.yml
@app.post("/webhooks/github")
async def github_webhook(request: Request):
headers = dict(request.headers)
payload = await request.body()
# Dedup
delivery_id = dedup.extract_delivery_id(headers)
if dedup.is_duplicate(delivery_id):
return {"status": "duplicate"}
# Verify signature
if not adapter.verify_signature(payload, headers):
return {"status": "invalid"}, 401
event = await adapter.parse_webhook(headers, payload)
repo_config = config.get_repo_config(org, repo)
# Enqueue handlers
if repo_config.release and repo_config.release.enabled:
await queue.enqueue(adapter, event,
lambda a, e: ReleaseHandler.process_event(a, e, repo_config.release))
if repo_config.auto_merge and repo_config.auto_merge.enabled:
await queue.enqueue(adapter, event,
lambda a, e: MergeHandler.process_event(a, e, repo_config.auto_merge))
return {"status": "queued"}All modules are configured via YAML in your org layover's config.yml:
release:
enabled: true
trigger: "tag_push" # tag_push | merge_label | both
tag_pattern: "v*" # fnmatch pattern
changelog_path: "CHANGELOG.md"
draft: falseauto_merge:
enabled: true
trigger_label: "auto-merge"
merge_method: "squash" # squash | merge | rebase
required_checks: ["test", "lint"]
delete_branch: truemirror:
enabled: true
primary_forge: "forgejo" # forgejo | github
mirror_url: "github.com/org/repo"
verify_on_push: true
max_drift_seconds: 300notifications:
enabled: true
channels:
- type: "slack" # webhook | slack | discord
url: "${SLACK_WEBHOOK_URL}"
events: ["release", "mirror_drift"]Org-level defaults are inherited by all repos. Repo-level config overrides org defaults. Mirror config is repo-specific only (no org inheritance).
MyOrg:
# Org defaults — inherited by all repos
auto_triage:
add_needs_triage_label: true
stale_management:
days_until_stale: 60
repositories:
my-repo:
release:
enabled: true
trigger: "tag_push"
# Inherits org-level auto_triage and stale_managementgit clone https://github.com/LangeVC/ops-engine.git
cd ops-engine
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest tests/ -v- Contributing: See CONTRIBUTING.md
- Security: See SECURITY.md
- Code of Conduct: See CODE_OF_CONDUCT.md
Licensed under the Apache License 2.0. See LICENSE for details.