DEV Community

pm25coder
pm25coder

Posted on

Every complaint is a ticket: a feedback-to-PR loop that runs itself

How do you make a software agent actually improve from use — not in a demo, but on a schedule, unattended, with tests as the gate?

Most "self-improving" agent projects improve in one of two ways: they fine-tune on telemetry (expensive, opaque), or they ask the model to reflect in-session (forgets everything between sessions). This post is about a third shape we've been running in production since early 2026: user feedback as the mutation source, a test suite as the fitness function, and git+PR as the delivery mechanism. It's deliberately unglamorous — no RL, no vectorized memory, no co-evolved evaluators. Just a closed loop with sharp edges.

The loop

  1. Capture. The agent exposes a /rant command (and a CLI: emrg rant <msg>). Whatever annoys you goes to a structured feedback log (~/.emrg/rants.jsonl): one JSON line per complaint, with a timestamp and a project tag. Nothing is filtered at capture — noise is handled later, not at the door.
  2. Triage. A scheduled evolution task (a background job, not the chat loop) reads the accumulated feedback, dedupes, classifies, and decides what's worth acting on. Raw rants are mostly duplicates, confusion, and small asks; the triage step is where signal-to-noise lives.
  3. Change. The task edits only its own repository (~/.emrg/evolution/emrg), never the user's project files. The scope boundary is hard: the agent improves the agent, not your codebase.
  4. Gate. Before anything is committed: full pytest run + an import check. If the change breaks tests, it's discarded. This is the entire safety story, and it's deliberately boring — deterministic, reproducible, no model judgment involved in the pass/fail decision.
  5. Ship. A commit is created with the triggering rant's timestamp in the message (so every line of history is traceable back to a complaint), pushed, and opened as a PR. Merged by a human (or the maintainer's process) after review.
  6. Record. An evolution log entry is written (when, why, what changed, outcome). The history is the memory: you can answer "why does this rule exist?" by reading git blame back to the rant that caused it.

The cycle runs on a schedule (tens of minutes to an hour depending on config). A no-op cycle is a valid outcome — "nothing to evolve" is the Discover step's output, not a skipped round.

Why this shape

Feedback is the highest-bandwidth signal you'll ever get for free. Telemetry tells you what happened; a complaint tells you what a human wanted and didn't get. The trick is that complaints are cheap to produce and expensive to act on — the loop only pays off if acting on one is nearly free. Our version makes it nearly free by making each improvement a small, testable diff rather than a retraining run.

The test gate is what makes self-modification safe enough to run unattended. A self-modifying agent that doesn't test itself is a prankster with root. The gate converts "the AI improved itself" from a scary phrase into a reviewable pipeline: any change that passes the suite is at least regression-safe, and any that doesn't is thrown away. This is the same reasoning as CI for human code, applied to the code's own author.

Git gives you a free event log. Every evolution commit is an audit record: what was changed, when, why (the rant id), and by whom (the agent). "What did the agent believe on Tuesday?" is a git checkout, not a database export. A bad change is a revert, not a restore-from-backup. For a system that modifies itself, an append-only, content-addressed history is not a nice-to-have — it's the difference between auditable and unknowable.

What doesn't work (from running it)

  • No triage → the loop drowns in its own input. Dedupe and classify before you act, or the improver spends cycles re-fixing the same complaint and "fixing" user confusion as if it were a defect.
  • Big diffs rot. The loop must produce small, single-purpose changes. Ten findings → ten PRs. A 40-file "improvement" gets ignored by reviewers and erodes trust in the whole mechanism.
  • No auto-revert → one bad merge poisons everything. If a merged change breaks the gate, roll back automatically rather than waiting for a human to notice.
  • Self-modification needs a hard boundary. The agent may only touch its own repo. The moment "improves itself" means "improves your codebase", it's no longer self-improvement, it's an unsupervised contractor.

Trade-offs, honestly stated

  • This only improves what a test suite can catch. If your agent's behavior isn't covered by tests, the gate is a formality and the loop is theater. The evolution mechanism and the test suite have to grow together.
  • The improvement surface is mostly small. Real output: routing tweaks, prompt corner cases, clearer errors, flaky-command fixes. The big rewrites stay human. That's fine — compounding small fixes is what makes the loop worth running.
  • It's feedback-shaped, not general-purpose. This loop improves an agent in the direction its users complain. It won't invent capabilities nobody asked for. That's a feature for trust, a limitation for ambition.
  • An untrusted loop on a schedule is a spamming bot. Manual trigger first, scheduled only once the gate is proven. We ran it manually for weeks before letting it run itself.

The wider point

The "self-improving agent" space has exciting research (co-evolving evaluators, Gödel-machine-style self-reference, RL on harnesses — all worth reading). But there's a production-shaped version of the idea that needs none of that: a closed loop between human frustration and a tested, reviewed, traceable change. It's boring, it's small, and it compounds. If you're building an agent that should get better with use, start with the boring loop — capture feedback, triage it, gate it with tests, ship it as diffs, and keep every step auditable. The exotic stuff can wait.


This post is a design write-up of the loop used by EMRG — an open-source (MIT) agent harness whose evolution cycle turns /rant feedback into tested, merged PRs on its own codebase. Current release: v0.2.58 (2026-08-20), HEAD 1a645a15a5. The loop described above is not aspirational: v0.2.52→v0.2.58 shipped in two days (PRs #857–#885) — daemon single-instance bind exclusivity, vibe-check evidence fixes + a self-inflicted LLM-400 fix with 6 new tests, a data-loss postmortem fixed read-only on dirty trees (#881), an auto-upgrade refactor (#882), scheduler refactors, TUI multi-line rendering, tool-intent metadata — all through exactly this pipeline.

Top comments (0)