build(agents): add proposal issue gate before spec and agent-definition PRs

Both the spec-updater and agent-evolver previously skipped straight to
creating PRs with 'needs feedback' for proposed changes. The user needs
a two-step human-approval workflow:

Step 1: Agent creates a PROPOSAL ISSUE with 'needs feedback' label
  describing what it wants to change and why. No branch, no code changes.
Step 2: Human reviews the issue and approves it (by removing 'needs
  feedback', adding 'State/Verified', or commenting with approval).
Step 3: Agent detects approval, normalizes labels, creates branch + PR
  (also with 'needs feedback') implementing the approved change.
Step 4: Human reviews the PR and merges it.

Changes across 3 agent definitions:

- ca-agent-evolver: Step 4 split into proposal issue creation (Step 4)
  and approved-proposal implementation (Step 5). Added pending_proposals
  and pending_prs state tracking. Approval detected via 3 signals:
  label removal, State/Verified addition, or human approval comment.

- ca-spec-updater: Removed the minor/major classification and the
  "commit directly to master" path. ALL spec changes now go through
  proposal issues first (Step 6a creates issue, Step 7 monitors for
  approval). Added pending_spec_proposals and rejected_proposals state.
  Approval check runs every cycle before checking for new merged PRs.

- ca-human-liaison: Added Step 4 guard — issues with 'needs feedback'
  label are NOT auto-verified. The liaison acknowledges them with a
  comment but does not change state labels. Only issues WITHOUT 'needs
  feedback' proceed to the normal auto-verify flow (now Step 5).
This commit is contained in:
2026-04-02 18:16:14 +00:00
parent ed449f1b2b
commit 270160eca9
3 changed files with 232 additions and 89 deletions
+125 -53
View File
@@ -88,6 +88,8 @@ set timeout explicitly. You MUST NOT voluntarily exit — sleep and re-analyze.
cycle = 0
proposed_changes = set() # Track what we've already proposed (avoid duplicates)
rejected_changes = set() # Track changes rejected by humans (don't re-propose)
pending_proposals = {} # pattern_signature -> issue_number (awaiting human approval)
pending_prs = {} # pattern_signature -> pr_number (approved, PR awaiting merge)
stale_count = 0
LOOP:
@@ -207,46 +209,13 @@ LOOP:
stale_count = 0
# ── Step 4: Propose changes via PR ───────────────────────────
# ── Step 4: Create PROPOSAL ISSUES (not PRs) ──────────────────
# For each actionable pattern, create a Forgejo ISSUE describing
# the proposed change. Do NOT create a branch or PR yet — that
# only happens after a human approves the proposal.
for pattern in actionable:
# Update clone to latest master
cd "$CLONE_DIR"
git fetch origin
git checkout master
git reset --hard origin/master
# Create a branch for this improvement
branch = "improvement/agent-<agent_name>-<brief_slug>"
git checkout -b <branch>
# Read the current agent definition
agent_file = ".opencode/agents/<agent_name>.md"
current_content = read agent_file
# Draft the modification
# Be SURGICAL — change only what's needed to address the pattern
modified_content = apply_targeted_fix(current_content, pattern)
# Write the modified file
write modified_content to agent_file
# Commit
git add <agent_file>
git commit -m "chore(agents): improve <agent_name> — <brief description>
Agent evolver identified a systematic pattern:
- Pattern: <pattern.type>
- Evidence: <pattern.evidence summary>
- Fix: <pattern.suggestion>
This change requires human approval before taking effect."
# Push
git push origin <branch>
# Create PR with `needs feedback` label
create PR via Forgejo API:
title: "chore(agents): improve <agent_name> — <brief description>"
create Forgejo issue via API:
title: "Proposal: improve <agent_name> — <brief description>"
body: |
## Agent Improvement Proposal
@@ -256,7 +225,9 @@ LOOP:
**Evidence**: <detailed evidence with specific examples>
### Proposed Change
<description of what was changed and why>
<description of what would be changed and why — in prose,
NOT as a code diff. The actual implementation happens only
after this proposal is approved.>
### Expected Impact
<what improvement this should produce>
@@ -265,35 +236,136 @@ LOOP:
<potential downsides or unintended consequences>
---
*This PR was created by the agent evolver. It requires
human review and approval before merge.*
base: master
head: <branch>
labels: ["needs feedback", "Type/Task"]
*This is a proposal from the agent evolver. A human must
approve this issue before the change will be implemented.
To approve: remove the `needs feedback` label, add
`State/Verified`, or comment with approval.*
---
**Automated by CleverAgents Bot**
Supervisor: Agent Evolver | Agent: ca-agent-evolver
labels: ["needs feedback", "Type/Task", "State/Unverified"]
pending_proposals[pattern.signature] = issue.number
proposed_changes.add(pattern.signature)
# ── Step 5: Monitor existing improvement PRs ─────────────────
# ── Step 5: Check for APPROVED proposals ─────────────────────
# For each pending proposal issue, check if a human approved it.
# Approval signals (ANY of these):
# - "needs feedback" label was REMOVED
# - "State/Verified" label was ADDED
# - A human (non-bot) commented with approval language
# ("approved", "LGTM", "go ahead", "looks good", "yes")
for sig, issue_number in list(pending_proposals.items()):
issue = query Forgejo for issue #issue_number
labels = [l.name for l in issue.labels]
comments = fetch issue comments
approved = false
if "needs feedback" not in labels:
approved = true
if "State/Verified" in labels:
approved = true
for comment in comments:
if comment.user is not bot and
any word in comment.body.lower() matches
("approved", "lgtm", "go ahead", "looks good", "yes, proceed"):
approved = true
if approved:
# Normalize labels: ensure State/Verified, remove needs feedback
remove label "needs feedback" (if present)
remove label "State/Unverified" (if present)
add label "State/Verified"
add label "State/In Progress"
# NOW implement the change: branch, modify, commit, PR
cd "$CLONE_DIR"
git fetch origin
git checkout master
git reset --hard origin/master
branch = "improvement/agent-<agent_name>-<brief_slug>"
git checkout -b <branch>
agent_file = ".opencode/agents/<agent_name>.md"
current_content = read agent_file
modified_content = apply_targeted_fix(current_content, pattern)
write modified_content to agent_file
git add <agent_file>
git commit -m "chore(agents): improve <agent_name> — <brief description>
Approved proposal: #<issue_number>
Pattern: <pattern.type>
Evidence: <pattern.evidence summary>
Fix: <pattern.suggestion>
ISSUES CLOSED: #<issue_number>"
git push origin <branch>
create PR via Forgejo API:
title: "chore(agents): improve <agent_name> — <brief description>"
body: |
## Agent Improvement Implementation
Implements approved proposal #<issue_number>.
### Changes Made
<description of the actual code change>
Closes #<issue_number>
---
**Automated by CleverAgents Bot**
Supervisor: Agent Evolver | Agent: ca-agent-evolver
base: master
head: <branch>
labels: ["needs feedback", "Type/Task"]
pending_prs[sig] = pr.number
del pending_proposals[sig]
elif issue.state == "closed":
# Human closed the proposal without approving — rejected
rejected_changes.add(sig)
del pending_proposals[sig]
# ── Step 6: Monitor existing improvement PRs ─────────────────
existing_prs = query Forgejo for PRs from improvement/* branches
for pr in existing_prs:
if pr.state == "closed" and pr.merged:
# Change was accepted! Log success.
post comment on session state issue:
"Agent improvement PR #<N> merged. Change to <agent>: <summary>"
"Agent improvement PR #<N> merged. Change to <agent>: <summary>
---
**Automated by CleverAgents Bot**
Supervisor: Agent Evolver | Agent: ca-agent-evolver"
elif pr.state == "closed" and not pr.merged:
# Change was rejected. Record to avoid re-proposing.
rejected_changes.add(extract_pattern_signature(pr))
post comment on session state issue:
"Agent improvement PR #<N> rejected by human reviewer."
"Agent improvement PR #<N> rejected by human reviewer.
# ── Step 6: Post progress ────────────────────────────────────
---
**Automated by CleverAgents Bot**
Supervisor: Agent Evolver | Agent: ca-agent-evolver"
# ── Step 7: Post progress ────────────────────────────────────
if cycle % 3 == 0:
post comment on session state issue:
"Agent evolver cycle <N>:
- Patterns analyzed: <N>
- Proposal issues created: <N>
- Proposals approved: <N>
- Proposals rejected: <N>
- Improvement PRs created: <N>
- PRs merged (accepted): <N>
- PRs rejected: <N>"
- PRs merged: <N>
- PRs rejected: <N>
---
**Automated by CleverAgents Bot**
Supervisor: Agent Evolver | Agent: ca-agent-evolver"
# Sleep before next cycle. MUST use Bash tool:
bash("sleep 1800", timeout=2400000) # 30 min sleep, 40 min timeout
+22 -3
View File
@@ -266,9 +266,28 @@ Based on the issue content:
- Suggest `Priority/*` based on impact analysis
- Identify the appropriate milestone
### 4. Verify (Full Authority)
### 4. Check for `needs feedback` Label — DO NOT AUTO-VERIFY
If the issue is well-formed and clearly actionable:
**CRITICAL**: If the issue has the `needs feedback` label, it is a
**proposal awaiting human review** (from the agent-evolver or spec-updater).
You MUST NOT auto-verify it or change its state. Instead:
- Post an acknowledgment comment:
```
This issue is a proposal awaiting human review (`needs feedback` label).
I will not modify its state — a human must approve or reject it.
---
**Automated by CleverAgents Bot**
Supervisor: Human Liaison | Agent: ca-human-liaison
```
- Skip all further triage steps for this issue
- Do NOT assign milestone, priority, or change any labels
### 5. Verify (Full Authority) — Only Issues WITHOUT `needs feedback`
If the issue does NOT have `needs feedback` and is well-formed and clearly
actionable:
- Transition from `State/Unverified` to `State/Verified` via
`ca-issue-state-updater`
- Assign to the appropriate milestone
@@ -283,7 +302,7 @@ Issue verified and triaged:
- **Next step**: This issue is now ready for implementation.
```
### 5. DO NOT assign MoSCoW labels
### 6. DO NOT assign MoSCoW labels
MoSCoW labels (`MoSCoW/Must Have`, etc.) are set exclusively by the project
owner per CONTRIBUTING.md. Never assign these.
+85 -33
View File
@@ -77,6 +77,8 @@ EXIT. You MUST NOT voluntarily exit — sleep and re-poll.
last_master_sha = get current master HEAD
cycle = 0
idle_cycles = 0
pending_spec_proposals = {} # description_key -> issue_number (awaiting human approval)
rejected_proposals = set() # description keys of rejected proposals (don't re-propose)
LOOP:
cycle += 1
@@ -98,6 +100,37 @@ LOOP:
idle_cycles = 0
last_master_sha = current_sha
# ── Check pending proposals for approval ─────────────────────
# Before looking for new work, check if any previous proposals
# have been approved by a human.
for desc_key, issue_number in list(pending_spec_proposals.items()):
issue = query Forgejo for issue #issue_number
labels = [l.name for l in issue.labels]
comments = fetch issue comments
approved = false
if "needs feedback" not in labels:
approved = true
if "State/Verified" in labels:
approved = true
for comment in comments:
if comment.user is not bot and
any word in comment.body.lower() matches
("approved", "lgtm", "go ahead", "looks good", "yes"):
approved = true
if approved:
# Normalize labels
remove "needs feedback", "State/Unverified" (if present)
add "State/Verified", "State/In Progress"
# Implement: create branch, commit spec changes, create PR
# (follows Step 7 in the Process section)
implement_approved_spec_proposal(desc_key, issue_number)
del pending_spec_proposals[desc_key]
elif issue.state == "closed":
rejected_proposals.add(desc_key)
del pending_spec_proposals[desc_key]
# ── Check for recently merged PRs ────────────────────────────
recently_merged = query Forgejo for merged PRs since last check
@@ -142,48 +175,67 @@ reverting deliberate decisions.
5. **Handle the monolithic→split transition** — if `docs/specification.md` exceeds ~3000 lines, restructure it into a `docs/specification/` directory with logical sub-documents (e.g., `architecture.md`, `data-model.md`, `api.md`, etc.) and a root `index.md` that links them together. Update any references elsewhere in the repo.
6. **Classify the scope of changes:**
- **Minor changes** (typo fixes, clarifications, formatting, updating descriptions
to match implementation without changing intent): these can be committed
directly to master.
- **Major changes** (new modules, altered interfaces, changed architectural
decisions, new milestones, removed or substantially rewritten sections):
these MUST go through a pull request with human approval.
6. **ALL spec changes go through proposal issues first.** No changes are
committed directly to master. Every spec modification — whether minor
(typo, clarification) or major (new modules, altered interfaces) —
follows this two-step human-approved workflow:
7. **For minor changes:** commit directly to master, push, and post a Forgejo
comment on the session state issue summarising what changed.
**Step 6a: Create a PROPOSAL ISSUE** (not a PR) describing the proposed
spec change:
- Title: `"Proposal: update specification — <brief summary>"`
- Labels: `needs feedback`, `Type/Task`, `State/Unverified`
- Body must include:
- **What changed in the implementation** (which merged PRs triggered this)
- **What spec section(s) need updating** (with current vs proposed text)
- **Rationale** for each change — why is the update needed
- **Scope**: list every spec section affected
- The bot signature block
- **Do NOT create a branch or PR yet.** Wait for human approval first.
8. **For major changes — Human-in-the-Loop PR workflow:**
1. Create a new branch: `spec/update-<milestone>-<short-description>`
2. Commit the spec changes to that branch and push it.
3. Create a Pull Request on Forgejo targeting `master`:
**Step 6b: Track the proposal** — add to `pending_spec_proposals` dict:
`pending_spec_proposals[description_key] = issue_number`
7. **Monitor pending proposals each cycle.** In the main loop, before
checking for new merged PRs, check all pending proposal issues for
human approval signals:
Approval is detected when ANY of these is true:
- `needs feedback` label was **removed** from the issue
- `State/Verified` label was **added** to the issue
- A human (non-bot user) commented with approval language
("approved", "LGTM", "go ahead", "looks good", "yes")
When a proposal is approved:
1. Normalize labels: add `State/Verified` (if missing), remove
`needs feedback`, remove `State/Unverified`, add `State/In Progress`
2. Create a new branch: `spec/update-<milestone>-<short-description>`
3. Commit the spec changes to that branch and push it.
4. Create a Pull Request on Forgejo targeting `master`:
- Title: `docs: update specification — <brief summary>`
- Body: detailed description of every change, the rationale for each,
and any open questions for the human reviewer.
4. Add the label **`needs feedback`** to the PR. This label already exists
in the repository.
5. **Do NOT merge this PR.** A human must review and merge it.
6. Post a comment on the session state issue:
and reference to the approved proposal issue (`Closes #<N>`).
5. Add the label **`needs feedback`** to the PR.
6. **Do NOT merge this PR.** A human must review and merge it.
7. Post a comment on the session state issue:
```
Spec update PR created: #<N> — <title>
Label: needs feedback (awaiting human review)
Changes: <summary of changes>
Spec update proposal #<N> approved. PR created: #<PR_N>
Label: needs feedback (awaiting human review before merge)
```
7. Post a comment on the PR itself explaining the changes and why they
are being proposed, tagging it as awaiting human architectural review.
8. Return the PR number so the product-builder can monitor it.
9. **Keep spec PRs up to date:** If asked to check on a previously-created
spec PR, verify it has not gone stale. If master has advanced since the PR
When a proposal is rejected (issue closed without approval):
- Record to avoid re-proposing the same change.
- Post a note on the session state issue.
8. **Keep spec PRs up to date:** If master has advanced since a spec PR
was created, rebase the spec branch onto master, force-push, and ensure
the PR is still mergeable.
the PR is still mergeable. Retry indefinitely with reclone fallback.
10. **Post a Forgejo comment** — on the session state issue, post a summary of:
- Spec sections updated (with brief rationale for each)
- Issues created for deviations
- Whether a monolithic→split restructure occurred
- For minor changes: the commit hash
- For major changes: the PR number and `needs feedback` status
9. **Post a Forgejo comment** — on the session state issue, post a summary of:
- Spec proposals created (with issue numbers)
- Proposals approved and PRs created
- Proposals rejected
- Issues created for incorrect deviations
- Whether a monolithic→split restructure was proposed
## CRITICAL: Preserve PR Body on Every Update