Skip to main content

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 gh calls, 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 labels, lane definitions, and issue-body formats are adapter data passed in, not hardcoded.

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 TERMINAL
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 adaptergh/API → Item[] + ObservedWorld (PAT via env var)
  • GitHub executor adapterReconcilerResult → merge, comment, create PR (PAT via env var)
  • Pluggable model interface — a ModelProvider protocol (see below)
  • CLIgroomer 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

MIT. See LICENSE and NOTICE.

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

nousergon_groomer-0.5.0.tar.gz (77.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

nousergon_groomer-0.5.0-py3-none-any.whl (56.2 kB view details)

Uploaded Python 3

File details

Details for the file nousergon_groomer-0.5.0.tar.gz.

File metadata

  • Download URL: nousergon_groomer-0.5.0.tar.gz
  • Upload date:
  • Size: 77.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for nousergon_groomer-0.5.0.tar.gz
Algorithm Hash digest
SHA256 a6b5301e2759f7361e542e65bea1df1b9b62c1b99d648e59add47f05e37f297b
MD5 c9f40229d22f566a153de541bde292b6
BLAKE2b-256 62b4d635296aa69bba2a8bad1390892db8e0d1231ef49bdca7b6da9c9a145aef

See more details on using hashes here.

Provenance

The following attestation bundles were made for nousergon_groomer-0.5.0.tar.gz:

Publisher: publish.yml on nousergon/nousergon-groomer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file nousergon_groomer-0.5.0-py3-none-any.whl.

File metadata

File hashes

Hashes for nousergon_groomer-0.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 240181bb0235d3b22149a60aad77759bf51bb60cfc3fc642802d459f6797114e
MD5 2caac4ca4d92e6b192767d0f895428d5
BLAKE2b-256 ad5977eaed7688a935ffd03075c6aa2f2bc3852ef7c7ecf20e85bb1821ac47e4

See more details on using hashes here.

Provenance

The following attestation bundles were made for nousergon_groomer-0.5.0-py3-none-any.whl:

Publisher: publish.yml on nousergon/nousergon-groomer

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.11.1

2 files

0.11.0

2 files

0.10.0

2 files

0.9.0

2 files

0.8.0

2 files

0.7.0

2 files

0.6.0

2 files

This release

0.5.0 This release

2 files

0.4.0

2 files

0.3.0

2 files

0.2.1

2 files

0.2.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page