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 (AGPL-3.0-only). 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

AGPL-3.0-only. See LICENSE and NOTICE.

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.2.0.tar.gz (68.1 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.2.0-py3-none-any.whl (54.2 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: nousergon_groomer-0.2.0.tar.gz
  • Upload date:
  • Size: 68.1 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.2.0.tar.gz
Algorithm Hash digest
SHA256 8a0915132301f701ab7656dfb3e79018092a2dc7b2758499ee2278d039ba956d
MD5 82af9d4feecdbf844a6a8774f0a3d744
BLAKE2b-256 329d604c720eb3b3128145ceff41adc80a8970f50d22a430b2834541492fd3fc

See more details on using hashes here.

Provenance

The following attestation bundles were made for nousergon_groomer-0.2.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.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for nousergon_groomer-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 061e2c3ed01728c3dec8cc52a4428e6d6f8e9dff082c29e12d7e7d028dd714d3
MD5 178925c0ebab5498b27ee6293e9b6fed
BLAKE2b-256 12a70740afab440179fbefae08b19ef3b276eeab99efe17bfa12dab49e3d40e4

See more details on using hashes here.

Provenance

The following attestation bundles were made for nousergon_groomer-0.2.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

0.5.0

2 files

0.4.0

2 files

0.3.0

2 files

0.2.1

2 files

This release

0.2.0 This release

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