nousergon-groomer
The deterministic control plane for the autonomous backlog-and-PR maintenance loop — the fixture-runnable core specified by the groom-sweep policy.
It computes, for every carried issue and PR, exactly one disposition — act, blocked, terminal, or undecidable — from declared dependencies evaluated against an observed snapshot of the world. The core is pure logic: no network, no credentials, no model. The operational harness (dispatch, merge execution, PAT) lives in the private layer.
What this repo IS
- Public (MIT). Framework, contracts, and eval logic — the "how rigorously we measure belief" tier — not strategy edge or secrets.
- Fixture-runnable (§8.1). The entire test suite runs over recorded JSON snapshots with no network, no credentials, no model.
- Pure logic over recorded state. Every function is a pure over its
inputs; the same
(items, world, store)always yields the same result.
What this repo is NOT
- No GitHub client. No PAT, no
ghcalls, no API writes. - No model calls. The control loop is deterministic (§5.4); the model is at the leaf, invoked by the private harness, not here.
- No dispatch / scheduler. When to run, which credentials to use, and how to perform a merge are the private harness's concern.
- No fleet-specific configuration. Gate families, lane definitions, and issue-body formats are adapter data passed in, not hardcoded.
- No label read as state. A
gate:*label is a status projection, never a source of truth: blocked-ness is derived from declared dependencies evaluated against the observed world, so a gate that has cleared unblocks its item with no actor and no label edit. A projection with no declaration behind it is a representation defect, and the disposition is UNDECIDABLE — never resolved in either direction.
Architecture
┌─────────────────────────────────────────────────────────┐
│ nousergon-groomer (PUBLIC — this repo) │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Deterministic core │ │
│ │ │ │
│ │ models.py Item, Dependency, Disposition │ │
│ │ dependency_ (Dependency, ObservedWorld) │ │
│ │ evaluator.py → DependencyEvaluation (§3) │ │
│ │ dependency_ transitive blocked-ness (§3.4)│ │
│ │ graph.py │ │
│ │ admission.py WIP ceiling + unblocked (§4) │ │
│ │ lane_classifier Gate A + Gate B pure fn │ │
│ │ disposition.py §5.1 total over ItemState │ │
│ │ observed_gen.py §5.5 skip optimization │ │
│ │ reconciler.py the loop — ties it together │ │
│ └──────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Fixture harness (§8.1) │ │
│ │ fixtures/ recorded PR/issue snapshots │ │
│ │ tests/ contract + property tests │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
published as a
versioned package
│
▼
┌─────────────────────────────────────────────────────────┐
│ alpha-engine-config/scripts (PRIVATE — not this repo) │
│ │
│ ┌──────────────────────────────────────────────────┐ │
│ │ Operational harness │ │
│ │ groom_driver.py dispatch + schedule │ │
│ │ *_merge_sweep.py merge execution (PAT) │ │
│ │ record_agent_ attribution recording │ │
│ │ merge.py │ │
│ │ model leaf generative candidate changes │ │
│ │ GitHub client fetch live state → snapshot │ │
│ └──────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
Policy alignment
| Policy § | Property | Implementation |
|---|---|---|
| §3 | Blocked-ness derived, never asserted | dependency_evaluator.py — pure fn over (Dependency, ObservedWorld) |
| §3.1 | Declaration validated at write boundary | models.Dependency — rejects empty/unevaluable at construction |
| §3.3 | Spec/status stored separately | models.Dependency (spec) vs DependencyEvaluation (status) — separate types |
| §3.4 | Dependencies compose transitively | dependency_graph.py — walks the closure, names the chain |
| §4.1 | WIP ceiling | admission.AdmissionController — bounds the queue, not the rate |
| §4.2 | Every carried item charged | admission.current_wip — counts drafts, blocked, in-review |
| §4.3 | PR opened only for unblocked | admission.can_admit — rejects blocked issues |
| §5.1 | One total reconciler | disposition.py — every ItemState maps to exactly one disposition |
| §5.3 | Idempotent and resumable | reconciler.py — re-running over identical state = same output |
| §5.4 | Deterministic core, model at leaf | the core has no model import; the leaf is a strategy interface |
| §5.5 | Observed-generation skip | observed_gen.py — records last-evaluated generation, skips unchanged |
| §8.1 | Core runs against fixtures | fixtures/ + tests/ — no credentials needed |
| F5 | No advanceable-but-unadvanced PRs | disposition.py — a PR the reconciler could advance but didn't is ACT |
Quick start
git clone https://github.com/nousergon/nousergon-groomer.git
cd nousergon-groomer
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest
That's it — 94 tests run over 8 fixture scenarios with no network, no credentials, and no model. If the last command exits 0, the core is correct against its recorded fixtures.
Using the core
from nousergon_groomer import (
Reconciler, ReconcilerConfig, Item, Dependency, DependencyKind,
ItemKind, ItemState,
)
from nousergon_groomer.dependency_evaluator import ObservedWorld
from nousergon_groomer.observed_gen import GenerationStore
# Record an issue with a declared dependency
issue = Item(
id="i1", kind=ItemKind.ISSUE, state=ItemState.OPEN_ISSUE_ACTIONABLE,
declared_dependencies=[
Dependency(kind=DependencyKind.S3_OBJECT, target="s3://bucket/key"),
],
)
# Observe the world (s3_objects is empty → the dep is unsatisfied)
world = ObservedWorld(s3_objects=set())
# Run one reconciliation pass
config = ReconcilerConfig(wip_ceiling=5, generation=1)
reconciler = Reconciler(config)
result = reconciler.reconcile([issue], world, GenerationStore())
# The issue is BLOCKED on the S3 object
assert result.items[0].disposition.kind.value == "blocked"
Fixtures
The fixtures/ directory holds JSON scenarios that record item
populations, observed worlds, and expected dispositions. Each is a
self-contained proof that the core produces the documented outcome for
that scenario:
| Fixture | Scenario | Expected |
|---|---|---|
clean_green_lane_pr |
green PR with a lane label | ACT automerge |
red_ci_pr |
PR with failing CI | ACT fix_ci |
blocked_issue |
issue blocked on an S3 object | BLOCKED |
transitive_blocked |
A blocked on B blocked on C | BLOCKED (chain) |
at_wip_ceiling |
WIP saturated, new issue | BLOCKED (admission) |
gate_labeled_pr |
green PR with a gate:* label and no declaration |
UNDECIDABLE |
gate_derived_dependency |
gated PRs whose gates are declared dependencies | ACT / BLOCKED / ACT |
do_not_groom |
item marked do-not-groom | TERMINAL |
undecidable |
unobservable dependency | UNDECIDABLE |
Roadmap — v0.2.0 (usable tool) and beyond
The v0.1.0 core is a library — pure logic over recorded state. To make it a usable tool that external users can point at their GitHub repo and run, v0.2.0 adds the operational adapters (public, credentials injected at runtime):
- GitHub snapshot adapter —
gh/API →Item[]+ObservedWorld(PAT via env var) - GitHub executor adapter —
ReconcilerResult→ merge, comment, create PR (PAT via env var) - Pluggable model interface — a
ModelProviderprotocol (see below) - CLI —
groomer run --repo foo/bar --config config.yaml --dry-run - Configuration system — YAML for lanes, gates, WIP ceiling, model tiers
The private layer (fleet-specific config, krepis router adapter, spot
bootstrap, prompt templates) stays in alpha-engine-config.
Model provider — provider-agnostic, never Anthropic
The model interface is a provider-agnostic protocol. The default implementation uses direct API calls to OpenAI-compatible endpoints — the common denominator across xAI (Grok), Moonshot (Kimi), Zhipu (GLM), DeepSeek, and other providers. Anthropic is never a default and never a dependency.
# pyproject.toml — optional dependencies, none required for the pure core
[project.optional-dependencies]
github = ["httpx>=0.24"] # snapshot + executor adapters
config = ["pyyaml>=6.0"] # config system
model = ["openai>=1.0"] # default OpenAI-compatible provider
cli = ["typer>=0.9"] # CLI entry point
# NOTE: "anthropic" is NEVER a dependency of this package.
The ModelProvider protocol:
class ModelProvider(Protocol):
def complete(self, prompt: str, *, model: str, temperature: float = 0.0) -> str:
"""Generate a completion via the provider's API."""
...
The default OpenAICompatibleProvider works against any provider that
exposes an OpenAI-compatible /v1/chat/completions endpoint (xAI, Moonshot,
Zhipu, DeepSeek, local models via vLLM/Ollama). Configuration is by base_url
- api_key + model_name — no vendor lock-in:
# config.yaml
model:
provider: openai-compatible
base_url: https://api.deepseek.com/v1
api_key_env: DEEPSEEK_API_KEY
model: deepseek-chat
The nousergon private layer implements this protocol with a krepis router adapter (proprietary). External users plug in any provider they choose.
Normative spec
The groom-sweep policy is the normative source. Every invariant in this code references its section number (e.g. §3, §5.1, §5.5) so the code and policy can be audited against each other.
Epic and issue breakdown
The v0.1.0 epic lives on alpha-engine-config as the single source of
truth: alpha-engine-config#5853.
Implementation issues are filed on this repo (#2–#13).
License
Relicensed from AGPL-3.0-only in 0.3.0 (2026-08-03): this tool is not monetised and nothing paid depends on operating it, so network-use copyleft protected revenue nobody intends to earn while taxing the only outcome the repo exists for — being installed and tried. Releases up to 0.2.1 remain available under AGPL-3.0-only.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file nousergon_groomer-0.7.0.tar.gz.
File metadata
- Download URL: nousergon_groomer-0.7.0.tar.gz
- Upload date:
- Size: 89.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d65860ebda3e640532c4fdf4de8d4adfc9ce3f60a0f10cad1323a8410131d9a1
|
|
| MD5 |
af9e1409122ce12f697e57a87ec09d34
|
|
| BLAKE2b-256 |
b25145ad2d802cdf22aaaaf43729748acd6282a491244143180ed715a2b4b3df
|
Provenance
The following attestation bundles were made for nousergon_groomer-0.7.0.tar.gz:
Publisher:
publish.yml on nousergon/nousergon-groomer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nousergon_groomer-0.7.0.tar.gz -
Subject digest:
d65860ebda3e640532c4fdf4de8d4adfc9ce3f60a0f10cad1323a8410131d9a1 - Sigstore transparency entry: 2340678213
- Sigstore integration time:
-
Permalink:
nousergon/nousergon-groomer@e5e5d541d8827b51581f821ddf45cfdcc397fb9e -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/nousergon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e5e5d541d8827b51581f821ddf45cfdcc397fb9e -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file nousergon_groomer-0.7.0-py3-none-any.whl.
File metadata
- Download URL: nousergon_groomer-0.7.0-py3-none-any.whl
- Upload date:
- Size: 63.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f203bca37b3b92de7f8cc69a25ef0520ee11c1147102bbbc85b26f3102b319d9
|
|
| MD5 |
24bce89699401a407c665cfcd7e3e2fb
|
|
| BLAKE2b-256 |
15fcf10a6f34e90aa07516fe17cd582b9de981e9508dfbd8420c6f9c8d609202
|
Provenance
The following attestation bundles were made for nousergon_groomer-0.7.0-py3-none-any.whl:
Publisher:
publish.yml on nousergon/nousergon-groomer
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
nousergon_groomer-0.7.0-py3-none-any.whl -
Subject digest:
f203bca37b3b92de7f8cc69a25ef0520ee11c1147102bbbc85b26f3102b319d9 - Sigstore transparency entry: 2340678222
- Sigstore integration time:
-
Permalink:
nousergon/nousergon-groomer@e5e5d541d8827b51581f821ddf45cfdcc397fb9e -
Branch / Tag:
refs/tags/v0.7.0 - Owner: https://github.com/nousergon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e5e5d541d8827b51581f821ddf45cfdcc397fb9e -
Trigger Event:
workflow_dispatch
-
Statement type: