Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
27e693c
A live run carries the identity of its own process group
slowernet Aug 19, 2026
93db03a
Inject a repo's secrets, and give only the pushing stages a token
slowernet Aug 19, 2026
bf53c3b
Find a working copy, or make one
slowernet Aug 19, 2026
90d705f
Prepare a repo on first touch, and block the item when something is m…
slowernet Aug 19, 2026
0ac6c56
Write Status, and retry the write that never landed
slowernet Aug 19, 2026
6781686
Claim an item, or say why not
slowernet Aug 19, 2026
b7d8cb1
Walk a run in its own thread, say what happened, and tear it down
slowernet Aug 19, 2026
d24233f
Reap a run against a verified identity, never against a pid alone
slowernet Aug 19, 2026
84c5987
Reconcile the board into work, once per tick
slowernet Aug 19, 2026
144b554
Sweep comments behind a cursor that only advances on a whole sweep
slowernet Aug 19, 2026
cd7bce7
A comment on a blocked item is an answer, and resumes its run
slowernet Aug 19, 2026
829facd
Give the two loops a home, and a way to say they are alive
slowernet Aug 19, 2026
d210b34
Doctor checks the board options, the clone roots, and the secrets modes
slowernet Aug 19, 2026
215218b
Correct two runbook steps that do not work as written
slowernet Aug 19, 2026
ea8d2e8
Two things the first real poll found that the tests could not
slowernet Aug 19, 2026
c860917
A restarted run picks up where it was, not at the top of its route
slowernet Aug 19, 2026
b22d1fc
A blocked run says which stage stopped and what it asked
slowernet Aug 19, 2026
69e0158
An answered run is running again, and says so
slowernet Aug 19, 2026
bb87e57
Say which of mill.md describes work that exists
slowernet Aug 19, 2026
b86f245
Tell triage what it judges, and let it refuse a hopeless spec
slowernet Aug 19, 2026
d75645d
Give notes a home that is not reference
slowernet Aug 19, 2026
4adc597
Wait for the rate-limit window rather than backing off into it
slowernet Aug 19, 2026
649c31f
Record what Plan 3a actually built, and what running it found
slowernet Aug 20, 2026
878767f
A launch the subscription refused is not the stage failing
slowernet Aug 20, 2026
04435da
A test that passes for the wrong reason is not coverage
slowernet Aug 20, 2026
a744948
Sort 57 review findings into six root causes and a queue
slowernet Aug 20, 2026
9045c54
Let the laptop fail the way the runner does
slowernet Aug 21, 2026
f01c30d
Decide what name mill's commits carry
slowernet Aug 21, 2026
16d9ff5
Ask the verdict, not the exit status, whether a limit refused a launch
slowernet Aug 21, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ Prohibitions only. Breaking one is a bug regardless of what a task appears to as
- Never write a call to `gh pr merge`. mill does not merge.
- Never post a comment except through `Mill::Github`.
- Never add a retry path around the two-strikes-per-stage counter, and never charge a strike for something the machine did to a stage. The ledger in the design doc is the only place that decides.
- Never signal a bare pid, and never signal at all without checking the recorded boot time first.
- Never signal a bare pid, and never signal a stored pgid without checking the recorded boot time
first. The one exception is a group this process spawned and still holds the handle for, which
`announce_spawn` may kill outright — it cannot have crossed a reboot.
- Never loosen a permission ruleset in `~/.mill/settings/`, and never add `--dangerously-skip-permissions` to the argv builder. `--permission-mode acceptEdits` on the writing stages is not that flag and is required — deny rules still bind under it.
- Never write an absolute path into a permission ruleset. Absolute deny rules are accepted silently and enforce nothing; rules are worktree-relative, and the working directory is what confines everything outside it.
- Never remove `--tools` or `--strict-mcp-config` from the argv builder, and never move confinement into an `allow` list — an allow list does not confine.
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,15 @@ rake mill:answer[2,"..."] # answer a blocked run and resume it
- [Setup runbook](docs/reference/setup.md) — the board, the tokens, the permission rulesets,
and a scratch repo to rehearse against

## Notes

`docs/notes/` holds work that is neither a spec, a plan, nor a rule to follow — investigations,
contracts for things not yet built, and comparisons worth keeping. Nothing here is binding.

- [The admin UI's frontend contract](docs/notes/admin-ui-frontend.md) — layout, design tokens,
the component catalog, and how the log tail polls. Plan 4 builds against it.
- [Agent convergence strategies](docs/notes/2026-08-13-agent-convergence-strategies.md)

## Stack

Ruby, Roda, Sequel, SQLite, Puma, Minitest, vanilla JS, stdlib for nearly everything else.
Expand Down
31 changes: 31 additions & 0 deletions app.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen-string-literal: true

require 'bundler'
Bundler.require

require_relative 'lib/mill'

# Plan 4 mounts the run list, the log tail and the kill switch beside this.
# Plan 3a needs one thing from the web layer: somewhere for the two worker
# threads to live, and a way to tell whether they are still alive.
class App < Roda
plugin :json

# Built here, started in config.ru. Starting threads as a side effect of
# `require` means anything that loads this file — a test, a console, a rake
# task — silently starts polling a real board.
#
# Built now rather than on first use because App.freeze makes the class
# immutable, and a request is too late to memoise anything onto it.
@workers = Mill::Workers.new

class << self
attr_reader :workers
end

route do |r|
r.root do
{ workers: App.workers.health, runs: Mill.db[:runs].where(status: 'running').count }
end
end
end
5 changes: 5 additions & 0 deletions config.ru
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
require './app'

App.workers.start

run App.freeze.app
9 changes: 9 additions & 0 deletions config/puma.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Puma defaults to 0.0.0.0, so mill always binds explicitly. On a laptop the
# loopback interface is the boundary; on a server MILL_BIND names the address
# the reverse proxy talks to, and Plan 4 adds the sign-in that makes that safe.
bind ENV['MILL_BIND'] || 'tcp://127.0.0.1:9494'

# One process: the poller and the supervisor are threads inside it, and a second
# worker process would run a second copy of both.
workers 0
threads 1, 8
17 changes: 17 additions & 0 deletions db/migrations/005_a_live_run_carries_its_own_identity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# A stage_attempts row is written when the attempt ends, in one insert. So while a
# stage is running there is no row to read, and the three columns that identify a
# live process have to sit beside the pgid that is already on the run.
#
# board_item_id is here for the same reason: writing Status needs the project item
# id, and the poller that found the item is not the thing that later reports the
# run finished.
Sequel.migration do
change do
alter_table :runs do
add_column :pid, Integer
add_column :pid_started_at, Integer
add_column :host_boot_at, Integer
add_column :board_item_id, String
end
end
end
71 changes: 71 additions & 0 deletions docs/notes/2026-08-13-agent-convergence-strategies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
Ah, the dreaded **Agentic Death Spiral**—where an overzealous reviewer agent meets a hyper-obedient author agent, and together they over-engineer a simple 10-line function into a 200-line monolith of defensive paranoia and bloat.

Because LLMs are trained to be helpful, reviewer agents will *always* find something if you ask them "what can be improved?" They will invent edge cases that will never happen in real life just to give you your money's worth.

To break this feedback loop and achieve convergence, you need to introduce structural constraints, strict heuristics, and clear decay mechanics into your multi-agent architecture.

---

## 1. The "Burden of Proof" Heuristic (Failing Test Requirement)

Speculative critique is the #1 cause of agent churn ("What if `user_id` is a list of strings instead of an int?").

* **The Rule:** The reviewer agent **cannot request a code change** based on logic or runtime behavior unless it can provide a self-contained, failing unit test that reproduces the bug on the current codebase.
* **Why it works:** If the reviewer agent can't write a test that fails, the critique is downgraded to an informational comment and the code is approved. This instantly eliminates 80% of defensive code bloat.

## 2. Hard Severity Gating & Actionability Shields

Do not let the author agent act on every comment. Force the reviewer agent to structure its output into strict severity buckets:

* **`BLOCKING` (Critical/Security/Correctness):** The author agent *must* fix this (e.g., SQL injection, memory leak, off-by-one error).
* **`NON-BLOCKING` (Nitpicks/Refactoring/Aesthetics):** Written to the PR notes for human context, but **hidden from the author agent** during auto-remediation loops.

If a review yields zero `BLOCKING` issues, the cycle converges immediately.

## 3. Offload Style & Safety to Deterministic Tools

LLMs are terrible arbiters of style, formatting, and strict typing because their opinion fluctuates with every call.

* **The Rule:** Never let an LLM review anything a linter, type checker, or static analysis tool (e.g., `Ruff`, `ESLint`, `Mypy`, `SonarQube`) can catch.
* Run deterministic tools **first**. If they pass, the LLM reviewer is *only* prompted to assess high-level semantic intent, business logic, and security risks.

## 4. Scope Locking & Feedback Decay

As iterations increase, narrow the reviewer's scope to prevent "churn creep" (where fixing Issue A introduces a minor style flaw that the reviewer flags in Round 2).

* **Round 1:** Review full PR diff.
* **Round 2:** Review *only* the specific lines modified in response to Round 1.
* **Round 3:** Reviewer prompt switches to "Strict Bug Hunt"—it is explicitly forbidden from commenting on architecture, readability, or defensive handling. It can only block if Round 2 introduced a breaking regression.
* **Round 4:** **Hard Circuit Breaker.** Fall back to a human or default-merge if tests pass.

## 5. "Bias Toward Approval" System Prompting

Modify your reviewer agent's system prompt to penalize rejections. Give it a high "cost" for requesting changes.

```markdown
You are a senior staff engineer conducting a PR review.

GOAL: Approve code that is correct, safe, and readable.
BIAS TOWARD MERGING: Perfection is the enemy of shipped software. Do not request changes for hypothetical edge cases, minor stylistic preferences, or speculative future needs.

RULES:
1. Accept code as long as it works, passes existing tests, and lacks severe security vulnerabilities.
2. Avoid suggesting defensive checks for inputs that are already typed or handled upstream.
3. If the code is "good enough," output STATUS: APPROVED.

```

---

## Summary Matrix

| Problem | Cause | Heuristic Solution |
| --- | --- | --- |
| **Defensive Bloat** | LLM inventing rare edge cases | Require a failing unit test to reject code. |
| **Endless Nitpicking** | LLMs always wanting to "help" | Gate feedback; only pass `BLOCKING` severity to Coder Agent. |
| **Scope Creep** | Refactoring fixes introduce new tweaks | Scope-lock reviews exclusively to newly touched diff lines. |
| **Flaky Formatting Debate** | LLM non-determinism | Offload formatting/types to native AST linters (`Mypy`, `Ruff`). |

---

How are you currently orchestrating the loop between the reviewer and author agents (e.g., custom Python script, LangGraph, AutoGen, or GitHub Actions)?
Loading
Loading