Skip to content

feat(supervisor): autonomous in-review sweep — poll PR checks, self-repair on failure, promote on merge - #435

Merged
getappz merged 2 commits into
masterfrom
task/65
Aug 10, 2026
Merged

getappz merged 2 commits into
masterfrom
task/65

Conversation

@getappz

@getappz getappz commented Aug 10, 2026 •

Copy link
Copy Markdown
Owner

Closes #65.

No autonomous loop drove an item from "in_review" (PR open) to "completed"
(PR merged), or repaired a PR with failing CI — item_check_merge only
promoted on-demand when something called it manually, and nothing polled it
periodically. Confirmed live: item #62's PR (#434) sat with broken CI for
its entire lifetime with zero autonomous response.

What changed

Adds supervisor::run_review_sweep, run every 60s by a new
spawn_supervisor_review_sweep daemon task (src/dashboard/server.rs),
mirroring the existing spawn_supervisor_discovery/run_discovery_tick
pattern.

It lists items in the in_review state group (no label needed — state
group is itself the query, and doubles as the concurrency guard: a
self-repair job's item_claim moves the item to started for its
duration, so an item with a repair already running never re-appears here).
For each item it polls PR/CI status via a new worktree::pr_ci_status,
which reuses the exact "total>0 && !pending" check-run classification
cli::git::wait_for_checks already uses (github::mcp::checks_wait_summary),
just applied once per tick instead of in a loop:

  • Merged → calls item_check_merge directly (bumped from pub(super)
    to pub(crate), same as item_claim) to promote to completed and
    clean up the worktree — no logic duplicated.
  • Failing → dispatches a self-repair job via the same agentflare-work
    in-process job type run_discovery_tick already uses (factored the
    job-enqueue call into a shared enqueue_work_job helper). Re-running
    agentflare work on an already-in_review item reclaims its existing
    worktree/branch and pushes a fix commit rather than starting over. A
    marker comment (## supervisor — CI self-repair dispatched) both informs
    the agent what's failing (via build_prompt's prior-discussion inclusion)
    and doubles as the attempt counter — once quota::decide::SELF_REPAIR_CAP
    (3) marker comments exist with CI still red, it gates the item with the
    needs-human-gate label instead of retrying forever.
  • Pending/Passing/Unknown → left alone for the next tick.

A job_in_flight check against the job queue closes the small race window
between enqueueing and the job actually reaching item_claim.

Test plan

  • cargo test --bin agentflare — full suite green except one
    pre-existing, unrelated failure (code::confirm::tests::fallback_scan_finds_matches_and_skips_target_dir,
    confirmed failing identically on unmodified master in this worktree)
  • cargo clippy --bin agentflare --tests — clean
  • cargo fmt --all --check — clean
  • 6 new supervisor.rs tests covering job_in_flight, cap-reached
    gating, already-gated quiet path, in-flight double-dispatch guard,
    and run_review_sweep's state-group filter + unknown-PR-status
    soft-fail
  • daemon smoke test for spawn_supervisor_review_sweep

Summary by CodeRabbit

  • New Features

    • Added automatic supervisor review sweeps every 60 seconds.
    • Merged pull requests are promoted automatically.
    • Failing CI checks can trigger self-repair jobs.
    • Added CI status reporting for pending, passing, failing, and merged pull requests.
  • Bug Fixes

    • Prevented duplicate repair jobs and enforced repair cooldowns and review limits.
    • Items requiring human attention are now gated appropriately.
  • Tests

    • Added coverage for review filtering, repair dispatch, duplicate prevention, gating, and empty-project startup behavior.

…epair on failure, promote on merge

Adds run_review_sweep, run every 60s by a new spawn_supervisor_review_sweep
daemon task, mirroring the existing spawn_supervisor_discovery pattern.

It lists items in the in_review state group and polls PR/CI status for each
via a new worktree::pr_ci_status (reusing github::mcp::checks_wait_summary's
existing check-run classification):

- Merged -> calls item_check_merge directly (now pub(crate), same as
  item_claim) to promote to completed and clean up the worktree.
- Failing -> dispatches a self-repair job via the same in-process
  agentflare-work job type run_discovery_tick already uses. Re-running
  agentflare work on an already-in_review item reclaims its existing
  worktree/branch and pushes a fix commit rather than starting over. A
  marker comment both informs the agent what's failing (via build_prompt's
  prior-discussion inclusion) and doubles as the attempt counter enforcing
  quota::decide::SELF_REPAIR_CAP before gating the item for a human instead
  of retrying forever.
- Pending/Passing/Unknown -> left alone for the next tick.

A job_in_flight check against the job queue closes the race window between
enqueueing and the job actually reaching item_claim; the in_review state
group itself (which a claimed job's item_claim moves the item out of) is
the primary guard against double-dispatch.

Agentflare-Agent: claude-code
Agentflare-Branch: task/65
Agentflare-Item: 65
@coderabbitai

coderabbitai Bot commented Aug 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The supervisor now polls in-review items every 60 seconds, classifies pull-request CI status, promotes merged items, and dispatches bounded self-repair jobs. The dashboard starts the sweep during server initialization and includes smoke and behavior tests.

Changes

Supervisor review automation

Layer / File(s) Summary
CI status and merge contract
src/worktree.rs, src/mcp_server/item.rs
pr_ci_status classifies merged, pending, passing, failing, and unknown pull-request states. item_check_merge is now crate-visible for supervisor use.
Work-job and repair dispatch
src/supervisor.rs
Normal dispatch and self-repair use shared queueing. Self-repair checks existing jobs, cooldowns, confirmed agents, repair attempts, and human-gate state.
Review sweep processing and validation
src/supervisor.rs
run_review_sweep processes in_review items, promotes merged items, skips non-actionable statuses, and dispatches failing-CI repairs. Tests cover filtering, duplicate prevention, repair markers, and human gating.
Periodic server execution
src/dashboard/server.rs
The dashboard starts a 60-second blocking review-sweep task and includes an async smoke test for startup execution.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DashboardServer
  participant SupervisorSweep
  participant GitHubChecks
  participant WorkQueue
  DashboardServer->>SupervisorSweep: start periodic review sweep
  SupervisorSweep->>GitHubChecks: classify pull-request status
  GitHubChecks-->>SupervisorSweep: return CI or merge status
  SupervisorSweep->>WorkQueue: enqueue eligible self-repair job
  SupervisorSweep-->>DashboardServer: report sweep result
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements supervisor review automation, but issue #65 requires ponytail-review diff scoping and shared review logic. Link the PR to the correct supervisor automation issue, or implement issue #65 requirements for uncommitted, branch, and commit-range review scopes.
Out of Scope Changes check ⚠️ Warning The implementation is outside the scope of directly linked issue #65, which concerns ponytail-review diff-source selection. Remove the unrelated supervisor changes or update the linked issue to document the supervisor review sweep objectives.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the autonomous supervisor sweep, CI polling, self-repair, and merge promotion implemented by the PR.
Description check ✅ Passed The description explains the changes and test results clearly, but it omits the template's Notes for reviewers section.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task/65

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
src/worktree.rs (1)

104-132: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider deriving is_pr_merged from pr_ci_status.

pr_ci_status repeats the whole PR-resolution preamble of is_pr_merged (Lines 68-88): branch name, RepoId::resolve_from_remote, Client::new, pulls::find_existing, and the merged check. The two functions must stay in sync for the sweep and for item_check_merge to agree on "merged".

is_pr_merged can delegate:

♻️ Proposed consolidation
 pub fn is_pr_merged(item: &agentflare_backend::item::Item, repo_root: &Path) -> bool {
-    let branch = format!("task/{}", item.sequence_id);
-    let Some(repo) = RepoId::resolve_from_remote(repo_root) else {
-        return false;
-    };
-    let client = match crate::github::Client::new() {
-        Ok(c) => c,
-        Err(_) => return false,
-    };
-    match crate::github::pulls::find_existing(&client, &repo, &branch) {
-        Ok(Some(pr)) => pr.merged_at.is_some(),
-        Ok(None) => false,
-        Err(e) => {
-            eprintln!(
-                "worktree: could not check merge status for item {}: {e}",
-                item.id
-            );
-            false
-        }
-    }
+    matches!(pr_ci_status(item, repo_root), PrCiStatus::Merged)
 }

This costs one extra check-runs call only when the PR is not merged, because pr_ci_status returns Merged before it fetches checks.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/worktree.rs` around lines 104 - 132, Refactor is_pr_merged to delegate to
pr_ci_status and return true only when the result is PrCiStatus::Merged,
removing its duplicated branch/repository/client/PR resolution and merged-check
logic. Preserve pr_ci_status’s existing behavior and avoid additional checks
beyond its early merged return.
src/supervisor.rs (1)

278-346: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Sweep structure and fixtures look sound; the promote path has no test.

run_review_sweep reads items, states, and labels in one with_backend_db closure, then runs the blocking GitHub calls outside the lock. That matches the pattern used by run_discovery_tick and by item_claim.

The new fixtures cover unknown status, dispatch, cap, already-gated, and in-flight. promote_merged_item (Lines 348-360) has no test. It parses item_check_merge's JSON and returns promoted. If item_check_merge's response key ever changes, this silently counts every merged item as skipped. A test that seeds an in_review item and asserts the promoted == false / reason shape from item_check_merge would pin the contract without network access.

Also applies to: 348-360, 914-1195

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/supervisor.rs` around lines 278 - 346, Add a focused test for
promote_merged_item that seeds an in_review item and exercises the
item_check_merge response without network access. Assert the parsed promoted
boolean and reason fields match the expected response shape, including the false
case, so response-key changes cannot silently turn merged items into skipped
results. Reuse the existing sweep fixtures and test helpers.
src/dashboard/server.rs (1)

221-231: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Set MissedTickBehavior::Delay on the sweep ticker.

If a sweep exceeds interval, the default Burst behavior can run several sweeps back to back. Delay limits catch-up to one tick and schedules the following tick one interval later. It does not wait before the first post-overrun sweep; use a completion-based sleep if every sweep requires a full interval between starts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/dashboard/server.rs` around lines 221 - 231, Configure the ticker in the
spawned sweep loop to use tokio’s MissedTickBehavior::Delay immediately after
creating it. Keep the existing interval and sweep execution flow unchanged; only
alter missed-tick handling so overruns do not trigger back-to-back sweeps.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/supervisor.rs`:
- Around line 390-435: Update the cap handling in the supervisor sweep around
already_gated and the prior_attempts cap branch so an existing “CI self-repair
cap reached” comment also prevents repeated comments when the gate label is
absent. Ensure the cap path creates or otherwise applies the
NEEDS_HUMAN_GATE_LABEL before returning, rather than silently skipping it when
label_id_by_name lacks the entry. Add coverage for a cap case without that label
and verify a second sweep creates no additional comment.
- Around line 367-376: Update job_in_flight to use a queue-side active-job
lookup that searches queued and running jobs without the Queue::list 100-row
limit, adding or reusing a Queue method such as find_active_by_arg with SQL
filtering. Match only agentflare-work jobs whose first argument args[0] equals
item_id, rather than searching every argument position, and preserve the boolean
result used to prevent duplicate repair jobs.

In `@src/worktree.rs`:
- Around line 130-147: Update pr_ci_status and the checks_wait_summary flow so
completed check runs with a null conclusion remain Pending rather than Passing,
and incorporate commit statuses alongside check runs when classifying CI. Extend
list_check_runs or the surrounding GitHub actions logic to fetch statuses for
the same SHA, then ensure failing status-only gates produce Failed while
preserving Pending for absent or unresolved checks.

---

Nitpick comments:
In `@src/dashboard/server.rs`:
- Around line 221-231: Configure the ticker in the spawned sweep loop to use
tokio’s MissedTickBehavior::Delay immediately after creating it. Keep the
existing interval and sweep execution flow unchanged; only alter missed-tick
handling so overruns do not trigger back-to-back sweeps.

In `@src/supervisor.rs`:
- Around line 278-346: Add a focused test for promote_merged_item that seeds an
in_review item and exercises the item_check_merge response without network
access. Assert the parsed promoted boolean and reason fields match the expected
response shape, including the false case, so response-key changes cannot
silently turn merged items into skipped results. Reuse the existing sweep
fixtures and test helpers.

In `@src/worktree.rs`:
- Around line 104-132: Refactor is_pr_merged to delegate to pr_ci_status and
return true only when the result is PrCiStatus::Merged, removing its duplicated
branch/repository/client/PR resolution and merged-check logic. Preserve
pr_ci_status’s existing behavior and avoid additional checks beyond its early
merged return.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c5b97940-671a-4f10-a8cc-1864ac5d152c

📥 Commits

Reviewing files that changed from the base of the PR and between 24e86a2 and e910b9e.

📒 Files selected for processing (4)
  • src/dashboard/server.rs
  • src/mcp_server/item.rs
  • src/supervisor.rs
  • src/worktree.rs

Comment thread src/supervisor.rs
Comment on lines +367 to +376
fn job_in_flight(queue: &agentflare_jobs::Queue, item_id: &str) -> bool {
[
agentflare_jobs::JobState::Queued,
agentflare_jobs::JobState::Running,
]
.into_iter()
.filter_map(|state| queue.list(Some(state)).ok())
.flatten()
.any(|job| job.args.contains(&item_id.to_string()))
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

job_in_flight can miss an in-flight job, because Queue::list caps at 100 rows.

Queue::list runs ORDER BY created_at DESC LIMIT 100 (crates/agentflare-jobs/src/queue.rs Lines 241-265). job_in_flight therefore inspects only the 100 newest queued jobs and the 100 newest running jobs. If more than 100 jobs are queued, the guard returns false for an item that already has a job, and the sweep enqueues a duplicate repair job every tick. The claim lease bounds the damage, because the second job's item_claim returns Held, but the queue and the marker-comment count still grow on each tick.

A second, smaller point: job.args.contains(...) matches the item id in any argument position. Only args[0] is the item id for agentflare-work jobs. Match the command and the first argument instead.

🔧 Proposed narrowing
 fn job_in_flight(queue: &agentflare_jobs::Queue, item_id: &str) -> bool {
     [
         agentflare_jobs::JobState::Queued,
         agentflare_jobs::JobState::Running,
     ]
     .into_iter()
     .filter_map(|state| queue.list(Some(state)).ok())
     .flatten()
-    .any(|job| job.args.contains(&item_id.to_string()))
+    .any(|job| job.command == "agentflare-work" && job.args.first().is_some_and(|a| a == item_id))
 }

The LIMIT 100 blind spot needs a queue-side lookup, for example a Queue::find_active_by_arg method that filters in SQL rather than in Rust.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/supervisor.rs` around lines 367 - 376, Update job_in_flight to use a
queue-side active-job lookup that searches queued and running jobs without the
Queue::list 100-row limit, adding or reusing a Queue method such as
find_active_by_arg with SQL filtering. Match only agentflare-work jobs whose
first argument args[0] equals item_id, rather than searching every argument
position, and preserve the boolean result used to prevent duplicate repair jobs.

Comment thread src/supervisor.rs
Comment on lines +390 to +435
let already_gated = label_id_by_name
.get(NEEDS_HUMAN_GATE_LABEL)
.is_some_and(|gate_id| {
mcp.with_backend_db(|conn| agentflare_backend::item::list_labels(conn, &item.id))
.ok()
.and_then(Result::ok)
.is_some_and(|ids| ids.contains(gate_id))
});
if already_gated || job_in_flight(queue, &item.id) {
return false;
}

let prior_attempts = mcp
.with_backend_db(|conn| agentflare_backend::comment::list_by_item(conn, &item.id))
.ok()
.and_then(Result::ok)
.map(|comments| {
comments
.iter()
.filter(|c| c.body.starts_with(CI_SELF_REPAIR_MARKER))
.count() as u32
})
.unwrap_or(0);

if prior_attempts >= crate::quota::decide::SELF_REPAIR_CAP {
let _ = mcp.comment_impl(CommentRequest {
action: "create".into(),
item_id: Some(item.id.clone()),
body: Some(format!(
"## supervisor — CI self-repair cap reached\n\nFailing checks: {}. \
{} automatic repair attempt(s) already made with no green build — needs a human look.",
failed_checks.join(", "),
crate::quota::decide::SELF_REPAIR_CAP,
)),
..Default::default()
});
if let Some(gate_id) = label_id_by_name.get(NEEDS_HUMAN_GATE_LABEL) {
let _ = mcp.item_add_label(ItemRequest {
action: "add_label".into(),
id: Some(item.id.clone()),
label_id: Some(gate_id.clone()),
..Default::default()
});
}
return false;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

The cap branch posts a new comment on every tick when the needs-human-gate label does not exist.

already_gated at Lines 390-397 can only become true if label_id_by_name contains NEEDS_HUMAN_GATE_LABEL. Line 426 also adds the label only if that entry exists. If the project has no needs-human-gate label, then:

  1. The cap branch posts a "cap reached" comment.
  2. No label is applied, so already_gated stays false.
  3. prior_attempts stays at the cap, because the cap comment does not use CI_SELF_REPAIR_MARKER.
  4. The next sweep tick repeats step 1.

The result is one new comment per item every 60 seconds, forever, with no human-visible gate. run_review_sweep builds label_id_by_name from the project's existing labels only, so a project that never created this label hits this path.

Gate on the comment as well as the label, and create the label when it is missing.

🐛 Proposed guard
+const CI_SELF_REPAIR_CAP_MARKER: &str = "## supervisor — CI self-repair cap reached";
+
 fn self_repair_or_gate(
-    let prior_attempts = mcp
+    let comments = mcp
         .with_backend_db(|conn| agentflare_backend::comment::list_by_item(conn, &item.id))
         .ok()
         .and_then(Result::ok)
-        .map(|comments| {
-            comments
-                .iter()
-                .filter(|c| c.body.starts_with(CI_SELF_REPAIR_MARKER))
-                .count() as u32
-        })
-        .unwrap_or(0);
+        .unwrap_or_default();
+    let prior_attempts = comments
+        .iter()
+        .filter(|c| c.body.starts_with(CI_SELF_REPAIR_MARKER))
+        .count() as u32;
+    let already_capped = comments
+        .iter()
+        .any(|c| c.body.starts_with(CI_SELF_REPAIR_CAP_MARKER));
 
     if prior_attempts >= crate::quota::decide::SELF_REPAIR_CAP {
+        if already_capped {
+            return false;
+        }
         let _ = mcp.comment_impl(CommentRequest {
             action: "create".into(),
             item_id: Some(item.id.clone()),
             body: Some(format!(
-                "## supervisor — CI self-repair cap reached\n\nFailing checks: {}. \
+                "{CI_SELF_REPAIR_CAP_MARKER}\n\nFailing checks: {}. \
                  {} automatic repair attempt(s) already made with no green build — needs a human look.",

Add a test that covers the cap path with no needs-human-gate label in label_id_by_name, and assert that a second call adds no second comment.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/supervisor.rs` around lines 390 - 435, Update the cap handling in the
supervisor sweep around already_gated and the prior_attempts cap branch so an
existing “CI self-repair cap reached” comment also prevents repeated comments
when the gate label is absent. Ensure the cap path creates or otherwise applies
the NEEDS_HUMAN_GATE_LABEL before returning, rather than silently skipping it
when label_id_by_name lacks the entry. Add coverage for a cap case without that
label and verify a second sweep creates no additional comment.

Comment thread src/worktree.rs
Comment on lines +130 to +147
let Some(sha) = pr.head.as_ref().map(|h| h.sha.clone()) else {
return PrCiStatus::Unknown;
};
let checks = match crate::github::actions::list_check_runs(&client, &repo, &sha) {
Ok(c) => c,
Err(e) => {
eprintln!(
"worktree: could not fetch check status for item {}: {e}",
item.id
);
return PrCiStatus::Unknown;
}
};
let summary = crate::github::mcp::checks_wait_summary(&checks, 0);
let total = summary["total_checks"].as_u64().unwrap_or(0);
if total == 0 || summary["pending"].as_bool().unwrap_or(true) {
return PrCiStatus::Pending;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Confirm the PR head type and check-run classification inputs.
set -euo pipefail

fd -t f 'pulls.rs' src | xargs -r rg -n -C4 'pub struct|sha|git_ref'
fd -t f 'actions.rs' src | xargs -r rg -n -C4 'pub struct CheckRun|status|conclusion'
rg -n -C6 'wait_for_checks' src

Repository: getappz/agentflare

Length of output: 6472


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- candidate files ---'
fd -t f . src | rg '/(pulls|actions|mcp|worktree|models)\.(rs|toml)$' || true

printf '%s\n' '--- PullRequest and head definitions ---'
rg -n -C8 'struct PullRequest|struct.*Head|pub head|git_ref|sha' src

printf '%s\n' '--- check summary implementation ---'
rg -n -C12 'checks_wait_summary|total_checks|pending|conclusion|check_runs' src

printf '%s\n' '--- status/check API callers ---'
rg -n -C6 'list_check_runs|list_status|commit.*status|statuses|pr_ci_status' src

Repository: getappz/agentflare

Length of output: 50375


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- relevant files ---'
fd -t f . src/github | rg '(models|pulls|actions|mcp)\.rs$'

printf '%s\n' '--- pull request model ---'
rg -l 'struct PullRequest|struct PullRequestHead|pub struct PullRequest' src/github | while read -r f; do
  echo "FILE: $f"
  rg -n -C12 'struct PullRequest|struct PullRequestHead|pub head|git_ref|sha' "$f"
done

printf '%s\n' '--- action endpoints and check-run helper ---'
for f in $(fd -t f . src/github | rg '(actions|mcp)\.rs$'); do
  echo "FILE: $f"
  rg -n -C10 'list_check_runs|check_runs|checks_wait_summary|total_checks|pending|conclusion|status' "$f" || true
done

printf '%s\n' '--- worktree and callers ---'
sed -n '90,175p' src/worktree.rs
sed -n '1085,1165p' src/cli/git.rs

Repository: getappz/agentflare

Length of output: 25680


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
# Read-only model of the classification described in the review comment.
def classify(run):
    conclusion = run.get("conclusion")
    status = run.get("status")
    if status != "completed":
        return "pending"
    if conclusion not in {"success", "neutral", "skipped"}:
        return "failed"
    return "passing"

cases = [
    {"status": "completed", "conclusion": None},
    {"status": "completed", "conclusion": "failure"},
    {"status": "in_progress", "conclusion": None},
    {"status": "completed", "conclusion": "success"},
]
for case in cases:
    print(case, "=>", classify(case))
PY

Repository: getappz/agentflare

Length of output: 386


🌐 Web query:

GitHub REST API commits SHA check-runs commit statuses difference /commits/{ref}/check-runs /commits/{ref}/statuses

💡 Result:

The primary difference between the GitHub REST API commit statuses (/commits/{ref}/statuses) and check-runs (/commits/{ref}/check-runs) lies in their intended use cases, capabilities, and accessibility [1][2]. Commit Statuses Commit statuses are a simpler, legacy mechanism primarily designed for external services and integrations to report a binary state (success, failure, error, or pending) for a specific commit [3][1][2]. - Capability: Supports a state, description, target URL, and context string to differentiate multiple statuses [3]. - Access: Available to OAuth apps and authenticated users with appropriate repository permissions [4][5]. - Identification: Distinguished by a user-provided context string [3][6]. Check-runs Check-runs are part of the more modern Checks API, which provides a rich, detailed interface for CI/CD and other automated tools [6][1][5]. - Capability: Supports detailed output, line-specific code annotations, summary text, and multiple states/conclusions (e.g., neutral, cancelled, timed_out, action_required) [1][5][2]. They belong to a "check-suite" and are managed through the Checks API rather than the Commits API [6][5][7]. - Access: Write access is restricted exclusively to GitHub Apps [4][5][7]. OAuth apps and users can only view check-runs [4][5]. - Identification: Distinguished by an auto-generated check_run_id [6]. In summary, commit statuses are best for basic integrations where you simply need to signal a pass/fail state, whereas check-runs are the preferred standard for GitHub Apps providing integrated, detailed feedback within the GitHub UI [6][1][5]. If you are not building a GitHub App, you are generally limited to using commit statuses to create new status information [4][5].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
def rust_failed(conclusion):
    return conclusion is not None and conclusion not in {"success", "neutral", "skipped"}

def rust_summary(runs):
    pending = [r["name"] for r in runs if r["status"] != "completed"]
    failed = [r["name"] for r in runs if rust_failed(r.get("conclusion"))]
    return {"pending": bool(pending), "failed_checks": failed, "total_checks": len(runs)}

runs = [
    {"name": "completed-null", "status": "completed", "conclusion": None},
    {"name": "in-progress", "status": "in_progress", "conclusion": None},
    {"name": "failure", "status": "completed", "conclusion": "failure"},
]
summary = rust_summary(runs)
print(summary)
print("completed-null-only =>", rust_summary([runs[0]]))
PY

printf '%s\n' '--- PrCiStatus consumers ---'
rg -n -C10 'PrCiStatus::(Failing|Passing|Pending|Unknown|Merged)|pr_ci_status' src

Repository: getappz/agentflare

Length of output: 10030


Treat completed checks with no conclusion as pending.

PullRequest.head uses RefInfo, which exposes sha; no change is needed for that field. However, checks_wait_summary excludes a completed run with conclusion == null from both pending and failed_checks. pr_ci_status then returns Passing, so supervisor::run_review_sweep skips self-repair.

list_check_runs queries only /commits/{sha}/check-runs. It does not query /commits/{sha}/statuses. A failing status-only required gate is therefore invisible. If another check run exists, the PR can be classified as Passing; if no check runs exist, it remains Pending. Handle missing conclusions as pending and include commit statuses in the CI classification.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/worktree.rs` around lines 130 - 147, Update pr_ci_status and the
checks_wait_summary flow so completed check runs with a null conclusion remain
Pending rather than Passing, and incorporate commit statuses alongside check
runs when classifying CI. Extend list_check_runs or the surrounding GitHub
actions logic to fetch statuses for the same SHA, then ensure failing
status-only gates produce Failed while preserving Pending for absent or
unresolved checks.

@getappz
getappz merged commit 9c05039 into master Aug 10, 2026
16 checks passed
@getappz
getappz deleted the task/65 branch August 10, 2026 14:24
getappz pushed a commit that referenced this pull request Aug 11, 2026
Reconciles #63's per-project discovery-tick rewrite with #435's
autonomous in-review sweep (run_review_sweep/self_repair_or_gate),
both of which touched supervisor.rs independently after this branch
was cut.

- enqueue_work_job (new in #435, shared by dispatch_item and
  self_repair_or_gate) now takes an optional folder_path so #63's
  per-project dispatch can still thread its item's own project
  directory through, while self_repair_or_gate (still single-project
  via run_review_sweep's cwd-based resolve_project) passes None.
- Test additions from both branches kept intact and separated
  (seed_ready_item_in_project/its regression test from #63;
  throwaway_repo/seed_in_review_item/seed_gate_label and the six
  review-sweep tests from #435) rather than interleaved -- git's
  merge algorithm had conflated them due to structural similarity
  between two unrelated new functions.

All 19 supervisor:: tests pass, fmt clean, clippy clean (CI flags).

Agentflare-Agent: claude-code
Agentflare-Branch: task/63
Agentflare-Item: 63
getappz added a commit that referenced this pull request Aug 12, 2026
… CPU pressure (#459)

* wip: agentflare-resource-gate crate + supervisor/dashboard integration (uncommitted work rescued from task/435 worktree)

Agentflare-Agent: claude-code_2-1-228_harness
Agentflare-Branch: wip/resource-gate

* ## Host resource gate for autonomous dispatch (item #435)

Adds `agentflare-resource-gate`, a new crate providing a process-wide,
continuously-sampled host-pressure tier that gates autonomous dispatch
independently of the existing per-agent `auth_db` cooldown check.

**What it does:**
- `signals.rs` — samples global CPU usage via a *fresh* `sysinfo::System` per
  call (regression guard against a Sentry-reported panic when a long-lived
  `System` outlives a runtime core-count change), plus a lightweight
  server/container/headless heuristic (`AGENTFLARE_DEPLOYMENT` env override,
  k8s, `/.dockerenv`, no-display-on-Linux).
- `policy.rs` — pure `decide(signals, cfg) -> Policy` with tiers
  `Aggressive` (server/always-on) → `Normal` → `Throttled` → `Paused`, in a
  documented evaluation order (user override → server mode → CPU severity →
  CPU busy threshold), thresholds clamped so a malformed config can't
  silently disable or force-throttle dispatch.
- `gate.rs` — a `OnceLock`-backed singleton: `init_global()` starts a 30s
  background sampler (idempotent, safe to call more than once), and
  `current_policy()` is a cheap cached read defaulting to `Normal` if never
  initialized.
- `pool_size.rs` — the pre-existing `dashboard::concurrency` worker-pool
  sizing logic, relocated here as it's the same "how much can this host
  handle" concern (`work_max_concurrency`'s CPU+memory-aware sizing).

**Integration:** `supervisor::run_discovery_tick` and `self_repair_or_gate`
now take a `host_policy: Policy` parameter and skip dispatch (counting the
item as `waiting`, not vanishing it) when `host_policy.blocks_dispatch()` —
independent of, and alongside, the existing `is_cooling_down` agent check.
`dashboard/server.rs::run` calls `init_global()` once at daemon startup and
threads the live policy into both dispatch call sites.

**Scope decisions:**
- Battery/AC probing (present in the openhuman reference design) is
  deliberately deferred — agentflare's autonomous `work` runs mostly on
  always-on dev boxes/servers, and pulling in `starship-battery` adds a real
  macOS FFI dependency chain for a signal unlikely to matter. Documented in
  `signals.rs` as a `Policy`/`Signals` field addable later with no API break.
- The per-worker concurrency semaphore described in the item's "proposed
  shape" (hard-cap concurrent dispatch under `Throttled`/`Paused`) is not
  implemented — out of scope for the "Done when" bar, which only requires
  `current_policy()` existing and consulted by both dispatch paths plus a
  CPU-pressure test proving it blocks dispatch. Left as a natural follow-up.
- `src/supervisor.rs`'s 3 new `host_gate_tests` were split into
  `src/supervisor_host_gate_tests.rs` (via `#[path]`) purely to stay under
  the repo's 1500-line LOC gate — no logic change.
- `crates/agentflare-jobs/src/supervisor.rs` had picked up an unrelated
  `task/440` dead-code fix borrowed for local clippy testing (its own diff
  said "will be reverted") — reverted back to master's version; that fix
  belongs to `task/440`, not this item.

**Verification:** `cargo test -p agentflare --bin agentflare supervisor::` —
22/22 passed including the 3 new host-gate tests. `cargo test -p
agentflare-resource-gate` — 23/23 passed. `cargo clippy -p agentflare -p
agentflare-resource-gate --all-targets -- -A unsafe_code -A
clippy::pedantic` — zero warnings. A full `--workspace -D warnings` clippy
run is blocked by the pre-existing (master, Windows-only) `agentflare-jobs`
dead-code/unused-import issue that `task/440` already fixes separately —
intentionally left untouched rather than folding an unrelated fix into this
PR.

This branch also recovers uncommitted work-in-progress that was sitting in
this item's worktree from an earlier, incomplete session (never committed,
discovered during an unrelated branch/worktree cleanup pass) — the crate
and integration above are that work, finished and verified.

Agentflare-Agent: claude-code_2-1-228_harness
Agentflare-Branch: task/435
Agentflare-Item: 435

* fix(supervisor): resolve host-gate test module on Linux, guard inverted CPU thresholds, defer instead of skip

Three fixes on top of the resource-gate work:

- The host-gate test module used a #[path] override with a '..' traversal
  (src/supervisor/tests/../../). Windows resolved it lexically, but Linux
  walks the real path and those intermediate directories don't exist, so
  fmt/clippy/build all failed in CI. Moved the file to the location Rust's
  default module resolution already looks for and dropped #[path] entirely.

- policy::decide clamped cpu_busy_threshold_pct and cpu_severe_pct to 0..100
  independently, which still admits an inverted pair (busy >= severe). That
  collapses the tier ladder -- every reading above severe pauses before it
  can be judged merely busy, making Throttled unreachable. GateConfig's
  fields are public, so the ordering guard has to live in decide() rather
  than only in from_env(). Falls back to the shared default pair.

- self_repair_or_gate returned a bool, so a cooldown or host-pressure block
  was counted as 'skipped' -- which reads to an operator as a decision that
  won't be revisited, when in fact the next sweep retries it. Replaced with
  a SelfRepairOutcome enum and added ReviewSweepResult.waiting, mirroring
  DiscoveryTickResult.waiting from item #82.

Agentflare-Agent: claude-code_2-1-228_agent
Agentflare-Branch: task/435
Agentflare-Item: 435

* refactor(resource-gate): normalize thresholds at the env boundary too, not just in decide()

CodeRabbit's config.rs finding asked for the ordering guard in
GateConfig::from_env as well, which the previous commit only added to
policy::decide. That was the weaker half: env vars are the actual
user-facing input path, so from_env could hand back a config whose
threshold pair disagreed with what the gate would really enforce --
misleading to anything else reading those fields (a log line, a future
dashboard tile), even though decide() recovered internally.

Extracted the clamp-and-order logic into GateConfig::normalized() so there
is one implementation, applied at the env boundary and re-applied
(idempotently) in decide() -- still needed there because the fields are
public and a hand-built GateConfig never passes through from_env.

Agentflare-Agent: claude-code_2-1-228_agent
Agentflare-Branch: task/435
Agentflare-Item: 435
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ponytail#524] Review scoping: uncommitted changes, branch, or commit range

1 participant