Skip to content

perf: bound the remaining full-agent plan and change-set reads#3571

Merged
matthiasn merged 1 commit into
mainfrom
feat/bound-plan-changeset-reads
Jul 25, 2026
Merged

perf: bound the remaining full-agent plan and change-set reads#3571
matthiasn merged 1 commit into
mainfrom
feat/bound-plan-changeset-reads

Conversation

@matthiasn

@matthiasn matthiasn commented Jul 25, 2026

Copy link
Copy Markdown
Owner

Fourth in the "day planner must not degrade as history accumulates" series,
after #3564, #3566 and #3569. Closes out the load-everything-then-filter-in-Dart
pattern.

What was still unbounded

_plannerOwnsDay (day_agent_service.dart) called
getCaptureEventMetaByAgentId with no day filter and scanned the result with
metas.any(...). It runs on every day-agent identity resolution — that is,
ahead of every day-view read — which made it the hottest remaining unbounded
path, hotter than the two providers #3569 fixed. Its docstring claimed the
narrow projection "keeps per-wake cost flat instead of O(all captures)", which
is true per row and false overall: the row count and the json_extract per row
still scaled with the coordinator's whole capture history.

Because #3569 made captures day-indexed, this is now a single indexed lookup
capped at one row.

The plan writer loaded every dayPlan the agent had ever written to serve
a 7-day lookback. dayPlan already stored its day as the subtype, so it now
asks for exactly the window's days.

The change-set editor loaded every changeSet ever proposed to find the
pending ones. changeSet stores its status as the subtype, so the
confirmed/rejected history — which grows with every diff the user has ever
acted on — is no longer read at all.

New repository method

getEntitiesByAgentIdAndSubtypes (plural) for the range case, so a lookback is
one round trip instead of one query per day. Chunked through
sqliteInClauseChunks like the other batched reads.

Testing

1902 daily_os_next + 496 agents/database tests pass; analyzer and formatter
clean; every changed line covered.

Worth noting about the test changes: several stubs previously returned entities
regardless of what was asked for, so they would have kept passing even if the
production filter were wrong. They now resolve subtypes through
AgentDbConversions.entitySubtype — the same projection the writer uses — so a
fake can no longer disagree with the real table. The in-memory harness
repository got the same treatment.

No CHANGELOG entry — no user-visible behavior change.

Summary by CodeRabbit

  • New Features

    • Added support for retrieving entities matching multiple subtypes in a single query.
    • Improved day-plan, change-set, and capture lookups with more targeted filtering.
  • Bug Fixes

    • Excluded deleted entities and unrelated historical records from relevant reads.
    • Improved day ownership detection for captures on specific days.
  • Tests

    • Added coverage for multi-subtype queries, empty filters, deleted records, and day-scoped lookups.

Three reads still loaded an agent's whole history and filtered in Dart.

- _plannerOwnsDay called getCaptureEventMetaByAgentId with no day filter
  and scanned with metas.any(). It runs on every day-agent identity
  resolution — ahead of every day-view read — so it was the hottest
  remaining unbounded path. PR #3569 made captures day-indexed, so it is
  now a single indexed lookup limited to one row.
- The plan writer loaded every dayPlan the agent ever wrote to serve a
  7-day lookback; it now asks for exactly the window's days in one read.
- The change-set editor loaded every changeSet ever proposed to find the
  pending ones; changeSet stores its status as the indexed subtype, so
  the confirmed/rejected history is never read.

Adds getEntitiesByAgentIdAndSubtypes for the range case, so a lookback
costs one round trip rather than one query per day.
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a multi-subtype agent-entity repository query with SQLite-safe batching, updates daily-agent plan and capture lookups to use subtype indexes, and aligns unit and integration test repositories with the new filtering behavior.

Changes

Subtype-Scoped Agent Entity Reads

Layer / File(s) Summary
Repository multi-subtype API
lib/features/agents/database/..., test/features/agents/database/...
Adds chunked indexed queries for multiple subtypes, excluding deleted entities, and tests subtype filtering, empty inputs, deletion, ordering, and entity conversion.
Daily-agent subtype consumers
lib/features/daily_os_next/agents/service/day_agent_plan_editor.dart, lib/features/daily_os_next/agents/service/day_agent_plan_writer.dart, test/features/daily_os_next/agents/service/day_agent_plan_service_test.dart, test/features/daily_os_next/integration/day_agent_pipeline_harness.dart
Updates pending change-set and lookback day-plan reads to use subtype filters, with matching mocks and pipeline repository implementations.
Capture-based day ownership
lib/features/daily_os_next/agents/service/day_agent_service.dart, test/features/daily_os_next/agents/service/day_agent_service_test.dart
Checks planner ownership through day-scoped capture entity queries and updates ownership test stubs accordingly.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DayAgentPlanWriter
  participant AgentRepository
  participant AgentRepoCore
  participant SQLite
  DayAgentPlanWriter->>AgentRepository: Request day plans for lookback subtypes
  AgentRepository->>AgentRepoCore: Forward multi-subtype query
  AgentRepoCore->>SQLite: Run chunked indexed IN queries
  SQLite-->>AgentRepoCore: Return matching non-deleted rows
  AgentRepoCore-->>AgentRepository: Return converted entities
  AgentRepository-->>DayAgentPlanWriter: Return filtered day plans
Loading
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the PR’s main performance change: bounding full-agent plan and change-set reads.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/bound-plan-changeset-reads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@codecov

codecov Bot commented Jul 25, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.22%. Comparing base (069edde) to head (6f015fe).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3571   +/-   ##
=======================================
  Coverage   99.22%   99.22%           
=======================================
  Files        1781     1781           
  Lines      130336   130359   +23     
=======================================
+ Hits       129322   129345   +23     
  Misses       1014     1014           
Flag Coverage Δ
glados 14.11% <0.00%> (-0.01%) ⬇️
standard 98.97% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@matthiasn
matthiasn merged commit c87078c into main Jul 25, 2026
26 of 27 checks passed
@matthiasn
matthiasn deleted the feat/bound-plan-changeset-reads branch July 25, 2026 00:47

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/features/daily_os_next/agents/service/day_agent_plan_editor.dart (1)

63-88: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Backfill changeSet subtype-from-status from the schema change.

pendingPlanDiffsForDay now depends on agent_entities.subtype = 'pending' for changeSet rows, and AgentDbConversions.toEntityCompanion derives changeSet subtype from status, but existing legacy rows are not covered by the schema v17 day-subtype backfill. Add a migration/backfill for changeSet.subtype = json_extract(serialized, '$.status') or restore/remove the narrowed DB filter so pending diffs on old installs are not silently hidden.

🤖 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 `@lib/features/daily_os_next/agents/service/day_agent_plan_editor.dart` around
lines 63 - 88, Backfill legacy changeSet rows so their agent_entities.subtype is
populated from json_extract(serialized, '$.status') during the schema v17
migration, covering existing installs before pendingPlanDiffsForDay queries
them. Update the migration/backfill logic associated with the v17 day-subtype
change, or remove the narrowed subtype filter in pendingPlanDiffsForDay until
the backfill is guaranteed; preserve pending-only filtering for correctly
migrated rows.
🧹 Nitpick comments (2)
test/features/daily_os_next/agents/service/day_agent_plan_service_test.dart (1)

4698-4715: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

stubEntitiesWithChangeSets ignores agentId, unlike the neighboring bySubtype helper.

The stub matches only on subtype, not agentId. Today's pendingPlanDiffsForDay tests all use a single, non-Daily-OS _agentId, so this doesn't currently produce a false pass, but it diverges from the real repository contract and from bySubtype above (which does match agentId). If a future test in this group exercises the multi-owner path (coordinator + per-day agent), this stub would silently return the same entities for every owner, masking a real mismatch.

♻️ Proposed fix: match agentId too
         ).thenAnswer((invocation) async {
+          final requestedAgentId = invocation.positionalArguments.single as String;
           final subtype = invocation.namedArguments[`#subtype`] as String;
           return [
             for (final entity in entities)
-              if (AgentDbConversions.entitySubtype(entity) == subtype) entity,
+              if (entity.agentId == requestedAgentId &&
+                  AgentDbConversions.entitySubtype(entity) == subtype)
+                entity,
           ];
         });
🤖 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 `@test/features/daily_os_next/agents/service/day_agent_plan_service_test.dart`
around lines 4698 - 4715, Update stubEntitiesWithChangeSets to capture the
requested agentId from the getEntitiesByAgentIdAndSubtype invocation and filter
entities by both matching agentId and AgentDbConversions.entitySubtype(entity)
matching subtype, consistent with the neighboring bySubtype helper and
repository contract.
lib/features/agents/database/agent_repo_core.dart (1)

214-253: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Avoid forcing INDEXED BY in this query.

This method hardcodes INDEXED BY idx_agent_entities_agent_type_sub, while the single-subtype path above lets the drift query use the index naturally. SQLite rejects INDEXED BY if the named index is missing or unusable, creating a future breakage risk if the index is ever renamed/migrated. Drop the hint here to keep this read resilient.

🛡️ Proposed fix: drop the forced index hint
       final rows = await _db
           .customSelect(
             'SELECT * FROM agent_entities '
-            'INDEXED BY idx_agent_entities_agent_type_sub '
             'WHERE agent_id = ? AND type = ? '
             'AND subtype IN ($placeholders) '
             'AND deleted_at IS NULL '
             'ORDER BY created_at DESC, id DESC',
🤖 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 `@lib/features/agents/database/agent_repo_core.dart` around lines 214 - 253,
Remove the forced INDEXED BY idx_agent_entities_agent_type_sub clause from the
SQL in getEntitiesByAgentIdAndSubtypes, allowing SQLite to select an available
index naturally while leaving the query filters, ordering, and result conversion
unchanged.
🤖 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 `@test/features/daily_os_next/agents/service/day_agent_service_test.dart`:
- Around line 1133-1138: Update the repository verification in the
DayAgentService ownership probe test around getEntitiesByAgentIdAndSubtype to
assert the concrete limit value of 1 instead of allowing any named limit. Keep
the existing agent ID, capture type, and testDayId subtype matchers unchanged.

---

Outside diff comments:
In `@lib/features/daily_os_next/agents/service/day_agent_plan_editor.dart`:
- Around line 63-88: Backfill legacy changeSet rows so their
agent_entities.subtype is populated from json_extract(serialized, '$.status')
during the schema v17 migration, covering existing installs before
pendingPlanDiffsForDay queries them. Update the migration/backfill logic
associated with the v17 day-subtype change, or remove the narrowed subtype
filter in pendingPlanDiffsForDay until the backfill is guaranteed; preserve
pending-only filtering for correctly migrated rows.

---

Nitpick comments:
In `@lib/features/agents/database/agent_repo_core.dart`:
- Around line 214-253: Remove the forced INDEXED BY
idx_agent_entities_agent_type_sub clause from the SQL in
getEntitiesByAgentIdAndSubtypes, allowing SQLite to select an available index
naturally while leaving the query filters, ordering, and result conversion
unchanged.

In `@test/features/daily_os_next/agents/service/day_agent_plan_service_test.dart`:
- Around line 4698-4715: Update stubEntitiesWithChangeSets to capture the
requested agentId from the getEntitiesByAgentIdAndSubtype invocation and filter
entities by both matching agentId and AgentDbConversions.entitySubtype(entity)
matching subtype, consistent with the neighboring bySubtype helper and
repository contract.
🪄 Autofix (Beta)

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

Review profile: CHILL

Plan: Pro Plus

Run ID: 04cf1ead-b90c-41fa-aadf-8d7046884746

📥 Commits

Reviewing files that changed from the base of the PR and between 069edde and 6f015fe.

📒 Files selected for processing (10)
  • lib/features/agents/database/agent_repo_core.dart
  • lib/features/agents/database/agent_repository.dart
  • lib/features/daily_os_next/agents/service/day_agent_plan_editor.dart
  • lib/features/daily_os_next/agents/service/day_agent_plan_writer.dart
  • lib/features/daily_os_next/agents/service/day_agent_service.dart
  • test/features/agents/database/agent_repo_core_test.dart
  • test/features/agents/database/agent_repository_test.dart
  • test/features/daily_os_next/agents/service/day_agent_plan_service_test.dart
  • test/features/daily_os_next/agents/service/day_agent_service_test.dart
  • test/features/daily_os_next/integration/day_agent_pipeline_harness.dart

Comment on lines +1133 to +1138
() => repository.getEntitiesByAgentIdAndSubtype(
dailyOsPlannerAgentId,
type: AgentEntityTypes.capture,
subtype: testDayId,
limit: any(named: 'limit'),
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Assert the bounded ownership-query limit.

limit: any(...) allows the test to pass if the implementation regresses to the repository default of -1 and performs an unbounded read. Assert the concrete bound used by DayAgentService (expected to be 1) instead.

Suggested change
-          limit: any(named: 'limit'),
+          limit: 1,

The PR objective requires this ownership probe to remain bounded.

📝 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
() => repository.getEntitiesByAgentIdAndSubtype(
dailyOsPlannerAgentId,
type: AgentEntityTypes.capture,
subtype: testDayId,
limit: any(named: 'limit'),
),
() => repository.getEntitiesByAgentIdAndSubtype(
dailyOsPlannerAgentId,
type: AgentEntityTypes.capture,
subtype: testDayId,
limit: 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 `@test/features/daily_os_next/agents/service/day_agent_service_test.dart`
around lines 1133 - 1138, Update the repository verification in the
DayAgentService ownership probe test around getEntitiesByAgentIdAndSubtype to
assert the concrete limit value of 1 instead of allowing any named limit. Keep
the existing agent ID, capture type, and testDayId subtype matchers unchanged.

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