Skip to content

feat: project-level performance review (loopx Loop Engineering principle 7) - #404

Merged
getappz merged 5 commits into
masterfrom
feat/loop-engineering-performance-review
Aug 8, 2026
Merged

feat: project-level performance review (loopx Loop Engineering principle 7)#404
getappz merged 5 commits into
masterfrom
feat/loop-engineering-performance-review

Conversation

@getappz

@getappz getappz commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Summary

Closes the highest-leverage gap between agentflare and loopx's Loop Engineering principles — principle 7, "Loop Agents need performance review": join agentflare's existing, separately-tracked quantity/quality/cost/attention signals into one per-agent, per-project record, synced cross-workstation via the existing memory-sync mechanism (#400).

  • ask_events table + agentflare_backend::ask_event module — makes attention cost (times a human was asked) queryable instead of an ephemeral side effect of a lifecycle flip
  • quota::decide_for_supervisor now records an ask event whenever it gates a goal
  • review::performance_review() — joins quantity (completed items), quality (review scores, all-time), cost (Claude Code session rollup, whole-project), and attention (ask events) for one agent
  • agentflare review performance CLI command — computes the join and saves it to the memory store as a performance_review observation; agentflare memory sync propagates it to other workstations unchanged, no new sync channel

Deliberately scoped to principle 7 only — principle 3 (blocked-route visibility across workstations) is a real next step but depends on a prerequisite that doesn't exist yet: GoalLifecycle::Clear (the transition that un-gates a goal) has no caller anywhere in the codebase. See the plan doc (local, not committed — docs/ is gitignored) for the full gap analysis and out-of-scope notes.

Test plan

  • ask_event:: unit tests (record/count_since scoping)
  • quota:: tests (25, including new gated_lifecycle_records_an_ask_event)
  • performance_review join test (quantity/quality/cost/attention all asserted)
  • End-to-end dogfood: ran agentflare review performance against this repo's own agentflare project, confirmed the observation landed via agentflare memory observations
  • Full agentflare + agentflare-backend suite passes

Summary by CodeRabbit

  • New Features

    • Added review performance reports covering completed work, review quality, attention requests, and project costs over a configurable time period.
    • Supports human-readable and JSON report formats.
    • Performance reviews are saved for future reference.
    • Added tracking for “Ask” decisions, including reasons, questions, related work items, and timestamps.
  • Bug Fixes

    • Improved handling and verification of gated goals that require clarification.

@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The change adds persistent supervisor ask events and integrates them into ask decisions. It also adds performance review aggregation and a CLI action that reports and stores project-level agent metrics.

Changes

Ask Events and Performance Reviews

Layer / File(s) Summary
Ask-event persistence
crates/agentflare-backend/src/ask_event.rs, crates/agentflare-backend/src/db.rs, crates/agentflare-backend/src/lib.rs, crates/agentflare-backend/src/migrations/0007_ask_events.sql
Adds the AskEvent model, SQLite schema, migration registration, recording API, counting API, and filtering tests.
Supervisor ask recording
src/quota/decide.rs
decide_for_supervisor records metadata when a decision is Ask. Recording failures do not replace the decision result.
Performance review projection
src/review.rs
Adds PerformanceReview and aggregates completed items, quality scores, attention requests, and project cost within a time window.
Performance review CLI
src/mcp_server.rs, src/cli/review.rs
Adds the review performance action, output formats, project and repository resolution, and memory persistence for the generated review.

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

Sequence Diagram(s)

sequenceDiagram
  participant Supervisor
  participant decide_for_supervisor
  participant ask_event
  participant SQLite_ask_events
  Supervisor->>decide_for_supervisor: evaluate quota decision
  decide_for_supervisor->>ask_event: record ask metadata
  ask_event->>SQLite_ask_events: insert ask event
  SQLite_ask_events-->>ask_event: return event ID
  decide_for_supervisor-->>Supervisor: return Ask decision
Loading
sequenceDiagram
  participant CLI
  participant performance_review
  participant BackendDatabase
  participant MainDatabase
  participant Memory
  CLI->>BackendDatabase: open review database
  CLI->>performance_review: request project performance
  performance_review->>BackendDatabase: read completed items and attention requests
  performance_review->>MainDatabase: read quality scores
  performance_review-->>CLI: return PerformanceReview
  CLI->>Memory: save review observation
Loading

Possibly related PRs

  • getappz/agentflare#162: Introduces the backend extended by this change for ask-event persistence and performance metrics.

Suggested labels: enhancement, rust

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: adding project-level performance reviews for agents.
Description check ✅ Passed The description clearly explains the changes, scope, rationale, and tests, but it omits the template's Notes for reviewers section and explicit standard command checks.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 feat/loop-engineering-performance-review

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: 8

🧹 Nitpick comments (1)
src/review.rs (1)

859-876: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the window bounds and the agent filter.

The test passes since = 0 and until = i64::MAX, so no assertion exercises the completed_at >= since && completed_at <= until filter or the per-agent filter. Those two filters carry the whole correctness of the projection. Add a second case with an item completed outside the window, an ask event recorded before since, and an item assigned to another agent. Assert all three are excluded.

🤖 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/review.rs` around lines 859 - 876, Extend the performance_review test
around the existing review assertion to add records completed outside the
requested window, an ask event before since, and an item assigned to a different
agent. Invoke performance_review with bounded since/until values and assert
those three records are excluded while the in-window, matching-agent results
remain included.
🤖 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 `@crates/agentflare-backend/src/ask_event.rs`:
- Around line 50-61: Update count_since to accept an until timestamp and
constrain ask_events.created_at to be before it, then pass the review’s until
value from performance_review. Add a test covering an event after until and
verify it is excluded from the count.
- Around line 64-127: The existing test only exercises record and count_since
directly; add an integration test in the tests module that invokes
decide_for_supervisor with a supervisor Ask decision, then queries ask_events
and asserts the matching persisted event fields. Reuse seed_project and the
established in-memory connection setup, and verify the event is associated with
the expected project, item, agent, decision, question, and timestamp as
applicable.

In `@src/cli/review.rs`:
- Around line 265-271: Fix the cost attribution in the review performance flow
around cost_totals and project_cost_usd so --project does not use
AgentflareMcp::resolve_project_name() as its key. Resolve the backend project
record’s canonical cost key for the supplied project id and use it for the
lookup and saved observation; alternatively, explicitly document in the output
and --project help text that costs are always scoped to the current directory.
- Around line 295-304: Add a quality_verified field to PerformanceReview,
populate it from AgentScore when constructing the review, and include it in JSON
and persisted memory output. Update the quality_accuracy formatting in the
review CLI to print quality_verified as the verified numerator while retaining
quality_findings as the denominator.
- Around line 97-100: Update the days argument definition to use Clap’s ranged
i64 value parser with a minimum of 1, so --days rejects zero and negative values
before processing. Preserve the existing default value and field type.

In `@src/quota/decide.rs`:
- Around line 472-489: Rename gated_lifecycle_records_an_ask_event to reflect
that decide returns Ask with a gate question without persistence. Add a separate
integration test using decide_for_supervisor and AgentflareMcp that verifies the
ask event is recorded, reusing the existing ask_event persistence coverage
patterns.

In `@src/review.rs`:
- Around line 272-285: Update the review calculation around quantity_completed
and attention_asks to propagate or otherwise surface errors from list_by_project
and count_since instead of converting failures to zero values. Keep the
successful counting behavior unchanged, and ensure review generation cannot
persist or sync misleading zero metrics after a backend read failure.
- Around line 283-285: Update the attention_asks calculation in the review
projection to honor the requested until boundary by using a backend count query
that filters both since and until; preserve the existing zero fallback and u32
conversion, and update the relevant backend symbol such as count_since to accept
and apply the upper bound.

---

Nitpick comments:
In `@src/review.rs`:
- Around line 859-876: Extend the performance_review test around the existing
review assertion to add records completed outside the requested window, an ask
event before since, and an item assigned to a different agent. Invoke
performance_review with bounded since/until values and assert those three
records are excluded while the in-window, matching-agent results remain
included.
🪄 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: d8ce87e5-8742-4a20-ac37-f16737aeedb6

📥 Commits

Reviewing files that changed from the base of the PR and between 048f369 and d678e12.

📒 Files selected for processing (8)
  • crates/agentflare-backend/src/ask_event.rs
  • crates/agentflare-backend/src/db.rs
  • crates/agentflare-backend/src/lib.rs
  • crates/agentflare-backend/src/migrations/0007_ask_events.sql
  • src/cli/review.rs
  • src/mcp_server.rs
  • src/quota/decide.rs
  • src/review.rs

Comment on lines +50 to +61
pub fn count_since(
conn: &Connection,
project_id: &str,
agent: Option<&str>,
since: i64,
) -> Result<i64> {
Ok(conn.query_row(
"SELECT COUNT(*) FROM ask_events
WHERE project_id = ?1 AND created_at >= ?2 AND (?3 IS NULL OR agent = ?3)",
params![project_id, since, agent],
|r| r.get(0),
)?)

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

Bound ask-event counts by the review end time.

count_since only filters created_at >= since. src/review.rs, Lines 259-298 accepts until but cannot pass it to this API. Events after until inflate attention_asks in historical performance reviews.

Add an upper timestamp bound to this query. Pass until from performance_review. Add an exclusive-after-until test.

🤖 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 `@crates/agentflare-backend/src/ask_event.rs` around lines 50 - 61, Update
count_since to accept an until timestamp and constrain ask_events.created_at to
be before it, then pass the review’s until value from performance_review. Add a
test covering an event after until and verify it is excluded from the count.

Comment on lines +64 to +127
#[cfg(test)]
mod tests {
use super::*;

fn seed_project(conn: &Connection) -> String {
let workspace = crate::workspace::create(
conn,
crate::workspace::CreateWorkspace {
name: "ws".into(),
slug: "ws".into(),
owner_agent: None,
item_label: None,
},
)
.unwrap();
let project = crate::project::create(
conn,
crate::project::CreateProject {
workspace_id: workspace.id,
name: "proj".into(),
identifier: "proj".into(),
external_source: None,
external_id: None,
},
)
.unwrap();
project.id
}

#[test]
fn record_then_count_since_scopes_by_project_and_agent() {
let conn = crate::db::open_in_memory().unwrap();
let pid = seed_project(&conn);
record(
&conn,
&pid,
None,
"item-1",
Some("claude-code"),
"gated",
Some("q?"),
100,
)
.unwrap();
record(
&conn,
&pid,
None,
"item-2",
Some("opencode"),
"gated",
None,
200,
)
.unwrap();

assert_eq!(count_since(&conn, &pid, None, 0).unwrap(), 2);
assert_eq!(
count_since(&conn, &pid, Some("claude-code"), 0).unwrap(),
1
);
assert_eq!(count_since(&conn, &pid, None, 150).unwrap(), 1);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Test the supervisor persistence path.

record_then_count_since_scopes_by_project_and_agent tests direct database writes. It does not verify that a supervisor Ask decision creates an event. Add an integration test that calls decide_for_supervisor and then reads the matching ask_events row.

🤖 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 `@crates/agentflare-backend/src/ask_event.rs` around lines 64 - 127, The
existing test only exercises record and count_since directly; add an integration
test in the tests module that invokes decide_for_supervisor with a supervisor
Ask decision, then queries ask_events and asserts the matching persisted event
fields. Reuse seed_project and the established in-memory connection setup, and
verify the event is associated with the expected project, item, agent, decision,
question, and timestamp as applicable.

Comment thread src/cli/review.rs
Comment on lines +97 to +100
/// Window size in days ending now, for quantity/attention/cost
/// (quality is always all-time — see `scores`).
#[arg(long, default_value = "7")]
days: i64,

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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

clap 4 value_parser range i64 argument validation

💡 Result:

In clap 4, you can validate that an i64 (or other numeric) argument falls within a specific range by using the value_parser! macro combined with the.range method [1][2][3]. For basic numeric validation, clap provides RangedI64ValueParser (and similar variants for other types), which can be initialized via the value_parser! macro or by passing a range expression directly to value_parser [4][5][6][3]. Examples of how to apply this: Using the derive API: #[arg(long, value_parser = clap::value_parser!(i64).range(1..100))] pub value: i64, Using the builder API: let mut cmd = clap::Command::new("example").arg( clap::Arg::new("count").long("count").value_parser(clap::value_parser!(i64).range(1..100))); Key points to remember: 1. Range Expressions: You can use standard Rust range expressions like 1..100 (exclusive upper bound) or 1..=100 (inclusive upper bound) [5][6][3]. 2. Negative Numbers: If you need to accept negative numbers, you must additionally configure the argument using.allow_negative_numbers(true) or.allow_hyphen_values(true) on the Arg, otherwise clap may misinterpret a negative sign as a command-line flag [4][7]. 3. Under the hood: clap uses RangedI64ValueParser to handle these constraints, which ensures the input is parsed into the requested type and checked against the bounds before your application logic receives the value [4][5][3]. If you have validation requirements that go beyond simple numeric ranges (e.g., custom string formats or file checks), you can pass a custom function to value_parser that returns a Result<T, String> [8].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- clap declarations ---'
rg -n -C 3 '(^|\s)clap(\s*=|[[:space:]]*\{)|clap::|days:' --glob 'Cargo.toml' --glob 'Cargo.lock' --glob '*.rs' .
printf '%s\n' '--- review implementation ---'
sed -n '80,115p;270,305p' src/cli/review.rs

Repository: getappz/agentflare

Length of output: 19504


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- lockfiles and clap resolution ---'
find . -name Cargo.lock -print
if [ -f Cargo.lock ]; then
  rg -n -A8 '^name = "clap"$|^name = "clap_builder"$|^name = "clap_derive"$' Cargo.lock
fi
printf '%s\n' '--- performance execution path ---'
sed -n '115,285p' src/cli/review.rs
printf '%s\n' '--- clap parser usage patterns ---'
rg -n -C 2 'value_parser!|value_parser\s*=|\.range\(' --glob '*.rs' --glob 'Cargo.toml' .

Repository: getappz/agentflare

Length of output: 8224


🌐 Web query:

clap 4.6.1 value_parser!(i64).range(1..) RangedI64ValueParser docs

💡 Result:

In clap 4.6.1, clap::value_parser!(i64).range(1..) is a valid way to create a value parser that restricts input to a specific numeric range [1][2]. The clap::value_parser!(T) macro selects an appropriate ValueParser implementation based on the provided type T [2]. When i64 (or other supported numeric types like u8, u16, i32, u64) is used, the macro returns a RangedI64ValueParser (or RangedU64ValueParser for unsigned types), which provides a .range(range) method to narrow the allowed values [1][3][2]. Key points regarding this usage: - Range Syntax: The .range() method accepts any type that implements RangeBounds<i64> (e.g., 1.., 1..=10, ..100), allowing for flexible boundary definitions [1][3]. - Supported Types: This pattern works for built-in numeric types that map to RangedI64ValueParser or RangedU64ValueParser [4][2]. If you use a type that does not support the .range() method (such as usize in some older contexts or custom types), you may encounter a "method not found" error because those types might return an _AnonymousValueParser instead [5]. - Negative Values: If you intend to use ranges that include negative numbers, ensure the argument is configured to allow them (e.g., by setting .allow_negative_numbers(true) on the Arg), otherwise the parser may reject inputs starting with a hyphen [1]. Example usage:.value_parser(clap::value_parser!(i64).range(1..)) [1][2]

Citations:


Reject non-positive --days values.

days.max(1) queries one day, but the text renderer prints the raw value. Add value_parser = clap::value_parser!(i64).range(1..) to reject 0 and negative values. This syntax is supported by clap 4.6.1.

🤖 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/cli/review.rs` around lines 97 - 100, Update the days argument definition
to use Clap’s ranged i64 value parser with a minimum of 1, so --days rejects
zero and negative values before processing. Preserve the existing default value
and field type.

Comment thread src/cli/review.rs Outdated
Comment on lines +265 to +271
let today = chrono::Local::now().date_naive();
let cost_start = today - chrono::Duration::days(days.max(1) - 1);
let cost_totals =
crate::cost::summarize((cost_start, today), crate::cost::GroupBy::Project);
let cost_key = crate::mcp_server::AgentflareMcp::resolve_project_name();
let project_cost_usd =
cost_totals.get(&cost_key).map(|t| t.cost_usd).unwrap_or(0.0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

The cost figure ignores --project.

project is a backend project id, and the argument doc at lines 86-88 states it is not the repo identifier. resolve_project_name() (src/mcp_server.rs lines 655-667) returns a git-remote basename or the cwd basename. The cost rollup is therefore keyed by the current working directory, not by the --project the user passed. Running agentflare review performance --project <other-project-id> from this repo attributes this repo's spend to that other project, and the saved observation records it under that project id.

Resolve the cost key from the backend project record, or state in the printed output and in the --project help text that cost is always scoped to the current directory.

🤖 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/cli/review.rs` around lines 265 - 271, Fix the cost attribution in the
review performance flow around cost_totals and project_cost_usd so --project
does not use AgentflareMcp::resolve_project_name() as its key. Resolve the
backend project record’s canonical cost key for the supplied project id and use
it for the lookup and saved observation; alternatively, explicitly document in
the output and --project help text that costs are always scoped to the current
directory.

Comment thread src/cli/review.rs
Comment on lines +295 to +304
match review.quality_accuracy {
Some(acc) => println!(
" quality: {:.0}% ({}/{} verified, {} round(s), all-time)",
acc * 100.0,
review.quality_findings,
review.quality_findings,
review.quality_rounds
),
None => println!(" quality: no recorded review rounds"),
}

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 "verified" count prints the findings total.

review.quality_findings fills both the numerator and the denominator, so the line always reads "N/N verified". That contradicts the accuracy percentage printed on the same line: an agent at 50% accuracy renders as "50% (2/2 verified)". The Scores arm at lines 236-241 prints s.verified and s.findings correctly.

The root cause is in src/review.rs: PerformanceReview copies accuracy, findings, and rounds from AgentScore but drops verified. The JSON output and the persisted memory observation are missing it too. Add quality_verified: u32 to the struct, populate it from agent_score, and print it here.

🐛 Proposed fix

In src/review.rs:

     pub quality_accuracy: Option<f64>,
     pub quality_findings: u32,
+    pub quality_verified: u32,
     pub quality_rounds: u32,
         quality_findings: agent_score.as_ref().map(|s| s.findings).unwrap_or(0),
+        quality_verified: agent_score.as_ref().map(|s| s.verified).unwrap_or(0),
         quality_rounds: agent_score.as_ref().map(|s| s.rounds).unwrap_or(0),

Then here:

                             acc * 100.0,
-                            review.quality_findings,
+                            review.quality_verified,
                             review.quality_findings,
                             review.quality_rounds
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
match review.quality_accuracy {
Some(acc) => println!(
" quality: {:.0}% ({}/{} verified, {} round(s), all-time)",
acc * 100.0,
review.quality_findings,
review.quality_findings,
review.quality_rounds
),
None => println!(" quality: no recorded review rounds"),
}
match review.quality_accuracy {
Some(acc) => println!(
" quality: {:.0}% ({}/{} verified, {} round(s), all-time)",
acc * 100.0,
review.quality_verified,
review.quality_findings,
review.quality_rounds
),
None => println!(" quality: no recorded review rounds"),
}
🤖 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/cli/review.rs` around lines 295 - 304, Add a quality_verified field to
PerformanceReview, populate it from AgentScore when constructing the review, and
include it in JSON and persisted memory output. Update the quality_accuracy
formatting in the review CLI to print quality_verified as the verified numerator
while retaining quality_findings as the denominator.

Comment thread src/quota/decide.rs
Comment on lines +472 to +489
#[test]
fn gated_lifecycle_records_an_ask_event() {
let conn = test_conn();
let (pid, sid) = seed_project(&conn);
let goal = make_goal_item(&conn, &pid, &sid, GoalLifecycle::Gated, 0);
let todo = make_todo(&conn, &pid, &sid, &goal.id);

let decision = decide(&conn, &todo);
assert_eq!(decision.effective_action, EffectiveActionInternal::Ask);

// decide() itself is pure and does not write; recording happens in
// decide_for_supervisor, which needs an AgentflareMcp and is covered
// by ask_event's own unit test for the write path. This pins that
// decide() still reaches Ask for a gated goal, which
// decide_for_supervisor's Ask arm relies on to call
// ask_event::record.
assert!(decision.gate_question.is_some());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Rename or replace this test.

This test calls decide, which is pure. It cannot record an ask event. The test only verifies that a gated lifecycle returns Ask with a gate question.

Rename the test to describe that behavior. Add the persistence assertion through decide_for_supervisor in a separate integration test.

🤖 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/quota/decide.rs` around lines 472 - 489, Rename
gated_lifecycle_records_an_ask_event to reflect that decide returns Ask with a
gate question without persistence. Add a separate integration test using
decide_for_supervisor and AgentflareMcp that verifies the ask event is recorded,
reusing the existing ask_event persistence coverage patterns.

Comment thread src/review.rs
Comment on lines +272 to +285
let quantity_completed = agentflare_backend::item::list_by_project(backend_conn, project_id)
.unwrap_or_default()
.into_iter()
.filter(|i| i.assignee_agent.as_deref() == Some(agent))
.filter(|i| i.completed_at.is_some_and(|t| t >= since && t <= until))
.count() as u32;

let agent_score = scores(main_conn, repo)?
.into_iter()
.find(|s| s.agent == agent);

let attention_asks =
agentflare_backend::ask_event::count_since(backend_conn, project_id, Some(agent), since)
.unwrap_or(0) as u32;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Backend read failures become zeros without any signal.

list_by_project errors collapse to unwrap_or_default() and count_since errors collapse to unwrap_or(0), while scores errors propagate. A locked or corrupt backend DB therefore produces a review that reports quantity_completed: 0 and attention_asks: 0 as if they were measured values. The CLI then persists that review as a performance_review observation and syncs it across workstations, so the wrong zeros outlive the failed run.

Propagate the backend errors, or at least surface them to the caller.

🤖 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/review.rs` around lines 272 - 285, Update the review calculation around
quantity_completed and attention_asks to propagate or otherwise surface errors
from list_by_project and count_since instead of converting failures to zero
values. Keep the successful counting behavior unchanged, and ensure review
generation cannot persist or sync misleading zero metrics after a backend read
failure.

Comment thread src/review.rs
Comment on lines +283 to +285
let attention_asks =
agentflare_backend::ask_event::count_since(backend_conn, project_id, Some(agent), since)
.unwrap_or(0) as u32;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

attention_asks ignores until.

count_since filters only on created_at >= since (see crates/agentflare-backend/src/ask_event.rs lines 50-62). quantity_completed respects both since and until, and the returned projection carries an until field. For any call where until is not "now", the attention count covers a wider window than the rest of the review. The CLI always passes now, so this is latent today, but the function is public and takes an arbitrary until.

Either add an upper bound to the backend query, or state the limitation in the doc comment the same way cost and quality limitations are already stated.

📝 Minimal documentation-only option
-/// agent dimension) and quality is all-time (`score_events` isn't date-
-/// filtered) — both are reported as such, not silently narrowed.
+/// agent dimension) and quality is all-time (`score_events` isn't date-
+/// filtered) — both are reported as such, not silently narrowed. Note
+/// `attention_asks` is bounded by `since` only: `ask_event::count_since`
+/// has no upper bound, so an `until` in the past does not clip it.
🤖 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/review.rs` around lines 283 - 285, Update the attention_asks calculation
in the review projection to honor the requested until boundary by using a
backend count query that filters both since and until; preserve the existing
zero fallback and u32 conversion, and update the relevant backend symbol such as
count_since to accept and apply the upper bound.

@getappz
getappz force-pushed the feat/loop-engineering-performance-review branch from e3e5c13 to f42b096 Compare August 8, 2026 08:21
@getappz
getappz merged commit 868e009 into master Aug 8, 2026
16 checks passed
@getappz
getappz deleted the feat/loop-engineering-performance-review branch August 8, 2026 08:27
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.

1 participant