From b79d70926ae88a3b10a13e2e029bbb7f487b8cf4 Mon Sep 17 00:00:00 2001 From: Agando Date: Mon, 26 Oct 2026 14:50:54 +0100 Subject: feat(feedback): autonomous first-pass reviewer — decide what is provable, leave the rest MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The operator asked: "when I don't do anything, an agent can do it for me and I can just follow up or steer it." This is that agent, deterministic (no LLM): same input, same decision, and every reason is a grep or a commit hash that can be checked. spaces/newos4/scripts/newos4-feedback-autoreview.py — rules, first match wins: R1 duplicate same fingerprint already decided -> resolve R2 already-fixed identifiers absent from modules/**/*.js AND `git log -S` proves a commit removed them -> resolve R3 noise fetch/abort/probe patterns -> reject R4 still-present auto report + stack + identifiers still in code -> approve (max 2/day) R5 plan / R6 user report -> never decided Writes to the same append-only decisions.jsonl with actor=auto-reviewer, note + evidence; the applier's last-wins rule means any operator click overrides an agent decision. Kill switch: data/feedback/autonomy.json {"mode":"off"} (safe default, aggressive = cap 5). Cron job e31ac58a559e, 07:40 and 19:40, no_agent, silent unless it decided something. Fixture proof (6 cases, /tmp corpus, git repo with a real removal commit): RPT-A duplicate -> resolve (duplicate of already-decided RPT-B) RPT-C already-fixed -> resolve (goneFromCode removed from modules/ in fc4eaf3) RPT-D noise -> reject RPT-E still-present -> approve RPT-F user report -> SKIP RPT-G never existed -> SKIP <- caught the first draft, which called it "already fixed" Live dry-run on the real corpus: "nothing provable (0 left for the operator, mode=safe)" and `git log -S emptyTrash -- spaces/newos4/modules` resolves to 563a9be, i.e. the report I wrongly approved by hand tonight would have been auto-resolved correctly. Dashboard: agent-decided cards carry a 🤖 badge + the reason + "Any button below overrides it", and the tab shows "🤖 feedback agent: N decided on its own in its last run (safe mode, M left for you)". --- scripts/newos4-feedback-collect.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'scripts/newos4-feedback-collect.py') diff --git a/scripts/newos4-feedback-collect.py b/scripts/newos4-feedback-collect.py index 226a74c..c0711d9 100755 --- a/scripts/newos4-feedback-collect.py +++ b/scripts/newos4-feedback-collect.py @@ -17,6 +17,7 @@ STEER = os.path.join(ROOT, 'spaces/newos4/data/maintenance/steering.json') DECISIONS = os.path.join(ROOT, 'spaces/newos4/data/feedback/decisions.jsonl') APPLIED = os.path.join(ROOT, 'spaces/newos4/data/feedback/applied.json') # ledger written by the applier NETFAIL = os.path.join(ROOT, 'spaces/newos4/data/debug/network_failures.json') # ingest-filter counter +AUTOREVIEW = os.path.join(ROOT, 'spaces/newos4/data/feedback/autoreview-latest.json') def load(path, default): @@ -80,7 +81,7 @@ def plans_needing_decision(dec): os.path.getmtime(p), timezone.utc).isoformat(timespec='seconds'), 'excerpt': excerpt, 'tasks': tasks, 'status': status, 'decision': d.get('decision'), 'decided_at': d.get('decided_at') or d.get('ts'), - 'note': d.get('note', ''), + 'note': d.get('note', ''), 'actor': d.get('actor', ''), }) return items @@ -103,6 +104,7 @@ def reports_needing_decision(dec): 'created_at': r.get('created_at'), 'auto': bool(diag.get('auto')), 'new': rid in new_ids, 'decision': d.get('decision'), 'decided_at': d.get('decided_at') or d.get('ts'), 'note': d.get('note', ''), + 'actor': d.get('actor', ''), 'message': (r.get('body') or '')[:1500], 'page': diag.get('url') or '', 'version': diag.get('version') or '', 'ua': (diag.get('ua') or '')[:120], 'errors': [str(e)[:300] for e in errs[:3]], @@ -147,6 +149,7 @@ def main(): }, 'plans': plans, 'reports': reports, 'steering': steer[:20], 'recent_apply': recent_apply(8), + 'autoreview': {k: (load(AUTOREVIEW, {}) or {}).get(k) for k in ('generated_at', 'mode', 'decided', 'skipped')}, 'triage_checks': (triage.get('checks') or {}), 'triage_generated_at': triage.get('generated_at'), } -- cgit v1.2.3