Skip to content

feat: agent state compaction#3265

Merged
matthiasn merged 6 commits into
mainfrom
feat/agent_state_compaction
Jun 4, 2026
Merged

feat: agent state compaction#3265
matthiasn merged 6 commits into
mainfrom
feat/agent_state_compaction

Conversation

@matthiasn

@matthiasn matthiasn commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added "Agent memory compaction" flag (off by default) to fold older task-log entries into rolling agent memory.
    • Checklist items can be archived/restored via a reversible proposal flow.
    • Agent conversations show exact system prompts as expandable, deduplicated rows; system rows now surface as Milestone/Retraction/System Prompt.
  • Improvements

    • Task agents only update reports when content would materially change, reducing churn and speeding completion.
    • Task agents now fall back to a pending title-change proposal when initial title writes are blocked.

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

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

Adds cutoff-based agent input-log capture and LLM summarization compaction, integrates summarizer/compactor into TaskAgentWorkflow with content-addressed system-prompt persistence, migrates AgentDatabase to schema v11 with a partial unique index for agent_links, introduces checklist archival and a runtime compaction flag/UI/i18n, and updates tests and docs.

Changes

Agent compaction, DB v11 migration, archival, workflow, and UI

Layer / File(s) Summary
DB v11: compaction flag, partial-unique agent_links, migrations/tests
lib/database/*, lib/features/agents/database/*, test/features/agents/database/*
Adds enable_agent_compaction constant/default, registers the flag in DB defaults, increments Drift schema to v11, rebuilds agent_links with a partial unique index excluding message_payload, and updates migration/regression/fresh-install tests.
Projection substrate: event log, retractions, decision events, cutoff summaries
lib/features/agents/projection/*, test/features/agents/projection/*
Adds EventPosition/InputEvent/RetractionEvent/InputEventLog, projectInputEvents/visibleTailEvents, decision-event formatting/projection, SummaryCheckpoint cutoff-driven selection, TailLine/render helpers, and related projection tests.
Compactor + LLM summarizer + provider wiring
lib/features/agents/service/*, lib/features/agents/sync/*, lib/features/agents/state/*, test/features/agents/service/*, test/features/agents/sync/*, test/mocks/*
Introduces AgentLogLlmSummarizer, refactors AgentLogCompactor to use projected logs and cutoff/hysteresis folding with optional retainTokens, wires summarizer into providers, and extends tests and mocks for summarization/folding behavior.
Workflow integration & prompt/payload persistence
lib/features/agents/workflow/*, lib/features/ai/repository/ai_input_repository.dart, tests test/features/agents/workflow/*
Integrates compaction into wake flow, assembles compacted task-log or falls back to legacy inline context, persists system prompts content-addressed, adds buildTaskStateMarkdown, stabilizes running-entry durations, and updates proposal/ledger rendering and end-of-wake update_report logic.
Checklist archival: tool, builder, handler, strategy
lib/features/agents/tools/*, lib/features/agents/workflow/*, lib/features/ai/functions/*, tests test/features/ai/*
Extends update_checklist_items schema with isArchived, expands ChecklistItemStateResolver shape, updates redundancy detection and human-summary wording, and implements archival application and tests in the handler/strategy.
UI, flags, consts, i18n
lib/features/agents/ui/*, lib/features/settings/ui/*, lib/utils/consts.dart, lib/l10n/*, tests test/features/agents/ui/*
Disambiguates system-kind rows into SystemPrompt/Milestone/Retraction flavors, adds the compaction flag row and icon, adds localization keys/getters across locales, and updates related UI/provider tests.
Docs & release notes, planning
CHANGELOG.md, flatpak/*, lib/features/agents/README.md, docs/implementation_plans/*
Updates changelog and Flatpak release notes, expands agent compaction README with prompt invariant and state diagram, and adds an executor idempotency/fencing implementation-plan doc.

Sequence Diagram(s)

sequenceDiagram
  participant Workflow
  participant Compactor
  participant LLM as CloudInferenceAPI
  participant DB

  Workflow->>DB: read `enable_agent_compaction` flag
  Workflow->>Compactor: maybeCompact(agentId, budget, retainTokens, inline decisions)
  Compactor->>LLM: summarize(chunked tail, model, provider)
  LLM-->>Compactor: streamed summary text
  Compactor-->>Workflow: checkpoint (cutoff, coveredSources) + visible tail
  Workflow->>DB: upsert system prompt payload (content-digest)
  Workflow-->>DB: persist per-wake system message + user message/context
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • matthiasn/lotti#2689 — Related AgentActivityLog/_KindBadge UI work refined here to disambiguate system rows.
  • matthiasn/lotti#2727 — Related redundancy/no-op suppression for checklist update proposals; overlaps with ChangeSetBuilder redundancy logic.
  • matthiasn/lotti#3257 — Prior compaction/input-capture groundwork that this PR extends and integrates.

Poem

I nibble long logs, fold tails with care,
Summaries roll where old notes were bare.
Checklists tucked, archived with a hop,
Milestones shine; system prompts on top.
A rabbit hums — compact, neat, and spry. 🐇

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/agent_state_compaction

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces 'Agent memory compaction' to summarize older task-log entries into a rolling memory, keeping the wake context compact. It updates task agents to conditionally update reports only on material changes, allows proposing checklist item archivals, and displays exact system prompts in the conversation view. The review identified two key issues: a compile-time syntax error in agent_log_compactor.dart due to invalid Dart syntax (?loaded.event.contentDigest), and a schema mismatch caused by the missing idx_agent_links_active_to_type index definition in the agent_database.drift file.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread lib/features/agents/sync/agent_log_compactor.dart Outdated
Comment thread lib/features/agents/database/agent_database.drift

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

Caution

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

⚠️ Outside diff range comments (1)
lib/features/ai/functions/lotti_checklist_update_handler.dart (1)

141-146: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update validation hints to mention isArchived as a valid update field.

The handler now accepts isArchived, but these two guidance strings still only mention isChecked/title, which can mislead retry behavior.

Suggested patch
-          'Invalid or missing "items". Provide a JSON array of update objects: '
-          '{"items": [{"id": "...", "isChecked": true}]}',
+          'Invalid or missing "items". Provide a JSON array of update objects: '
+          '{"items": [{"id": "...", "isChecked": true}, {"id": "...", "isArchived": true}]}',
@@
-            'Item at index $i is not an object. Each item must be an object '
-            'with id and at least one of isChecked or title.',
+            'Item at index $i is not an object. Each item must be an object '
+            'with id and at least one of isChecked, title, or isArchived.',

Also applies to: 168-173

🤖 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/ai/functions/lotti_checklist_update_handler.dart` around lines
141 - 146, Update the user-facing validation/help strings in
lotti_checklist_update_handler.dart to include `isArchived` as a valid update
field; specifically change the message passed to _createErrorResult (the
"Provide a JSON array..." hint) and the other similar guidance string later in
the same handler so they list {"id": "...", "isChecked": true, "isArchived":
true} (and mention title if present) — search for the current strings that
mention only "isChecked" / "title" in the handler and add `isArchived` to both
messages.
🤖 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 `@docs/implementation_plans/2026-06-02_executor_idempotency_fencing_plan.md`:
- Line 128: The phrase "synced lease state + fence-rejection-on-reconnect,
degrades to this design" uses nonstandard/awkward wording; change the defer
rationale to clearly phrase that the system "needs lease state to be synced" (or
equivalent like "requires the lease state to be synced") and keep the rest of
the clause intact so it reads e.g. "needs lease state to be synced +
fence-rejection-on-reconnect, degrades to this design." Update the sentence
containing "synced lease state + fence-rejection-on-reconnect, degrades to this
design" accordingly to improve clarity.

In `@lib/features/agents/projection/compaction_summary.dart`:
- Around line 155-159: The body text normalization currently uses rawBody.trim()
which preserves embedded newlines; update the normalization in the compaction
logic (references: rawBody, body, editedTag and the similar block around lines
166-168) to collapse all newline and excessive whitespace into single spaces and
then trim so each entry is a single line (e.g., replace sequences of
newline/whitespace with a single space, then .trim()) to maintain the compacted
tail's line-oriented contract.

In `@lib/features/agents/projection/decision_events.dart`:
- Around line 27-33: The summary and reason may still contain embedded
newlines/tabs because only trim() is used; normalize internal whitespace for
entry.humanSummary and entry.reason so they become single-line strings (e.g.,
collapse all runs of whitespace to a single space) before using them to build
summary, trimmedReason and reasonSuffix in decision_events.dart; update the
variables summary and trimmedReason to perform this normalization (split/join or
regex replace) so the returned ledger line always stays on one line.

In `@lib/features/agents/README.md`:
- Around line 442-465: The README currently contradicts itself about whether the
read-path and summary-checkpoint flow are active behind the
enable_agent_compaction flag; reconcile the two sections so there's a single
source of truth by either (A) marking the earlier block as "Planned
Improvements" and noting that AgentLogCompactor.maybeCompact,
AgentLogCompactor.assembleContext, AgentLogLlmSummarizer and
buildTaskStateMarkdown are not yet wired into the live wake read-path, or (B)
updating the later "Planned Improvements" lines to state that these features are
already live when enable_agent_compaction is true (with
TaskAgentWorkflow.compactionEnabled remaining a test override); pick one
approach and make both sections consistent about the runtime behavior and flag
semantics.

In `@lib/features/agents/tools/agent_tool_registry.dart`:
- Around line 228-235: The item schema currently requires "id" but allows `{
"id": "..." }`; update the JSON schema for the checklist item (the schema object
inside the parameters for the checklist-update tool) to require "id" and also
add an anyOf constraint so at least one of isChecked, title, or isArchived is
present (e.g. keep required: ["id"] and add anyOf:
[{required:["isChecked"]},{required:["title"]},{required:["isArchived"]}]).
Apply the same change to the other identical item schema occurrence mentioned in
the comment so the validator rejects no-op items.

In `@lib/features/agents/workflow/task_agent_workflow.dart`:
- Around line 360-363: The DB read for enableAgentCompactionFlag can throw and
currently will abort wake execution; change the compactionOn initialization to
catch failures from journalDb.getConfigFlag and default to false so compaction
remains optional: when computing compactionOn (which currently uses
compactionEnabled ?? await journalDb.getConfigFlag(enableAgentCompactionFlag)),
wrap the await call in a try/catch (or use a safe helper) to return false on any
error, then continue constructing compactor as before (referencing compactionOn,
compactionEnabled, journalDb.getConfigFlag, enableAgentCompactionFlag, and
compactor).

In `@test/features/agents/workflow/task_state_markdown_test.dart`:
- Around line 62-65: Replace the brittle .singleWhere lookup in the test with a
safer .firstWhere (or .where(...).first) and include the closing parenthesis in
the match so you match the full "(id: ...)" token; specifically change the
expression on text.split('\n') that currently uses .singleWhere((l) =>
l.contains('(id: ${item.id}')) to e.g. .firstWhere((l) => l.contains('(id:
${item.id})'), orElse: () => throw StateError('no matching line for id
${item.id}')) and keep the existing expect assertion using item.completed.

---

Outside diff comments:
In `@lib/features/ai/functions/lotti_checklist_update_handler.dart`:
- Around line 141-146: Update the user-facing validation/help strings in
lotti_checklist_update_handler.dart to include `isArchived` as a valid update
field; specifically change the message passed to _createErrorResult (the
"Provide a JSON array..." hint) and the other similar guidance string later in
the same handler so they list {"id": "...", "isChecked": true, "isArchived":
true} (and mention title if present) — search for the current strings that
mention only "isChecked" / "title" in the handler and add `isArchived` to both
messages.
🪄 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

Run ID: 14544369-788b-4585-9f3d-3e4b9d03f7a1

📥 Commits

Reviewing files that changed from the base of the PR and between daa46b0 and 2ab3405.

📒 Files selected for processing (62)
  • CHANGELOG.md
  • docs/implementation_plans/2026-06-02_executor_idempotency_fencing_plan.md
  • flatpak/com.matthiasn.lotti.metainfo.xml
  • lib/database/journal_db/config_flags.dart
  • lib/features/agents/README.md
  • lib/features/agents/database/agent_database.dart
  • lib/features/agents/database/agent_database.drift
  • lib/features/agents/database/agent_database.g.dart
  • lib/features/agents/database/agent_repository.dart
  • lib/features/agents/projection/compaction_summary.dart
  • lib/features/agents/projection/decision_events.dart
  • lib/features/agents/projection/input_events.dart
  • lib/features/agents/projection/side_effect_key.dart
  • lib/features/agents/service/agent_log_llm_summarizer.dart
  • lib/features/agents/state/agent_providers.g.dart
  • lib/features/agents/state/agent_query_providers.dart
  • lib/features/agents/state/agent_workflow_providers.dart
  • lib/features/agents/state/agent_workflow_providers.g.dart
  • lib/features/agents/sync/agent_log_compactor.dart
  • lib/features/agents/tools/agent_tool_registry.dart
  • lib/features/agents/ui/agent_activity_log.dart
  • lib/features/agents/workflow/change_set_builder.dart
  • lib/features/agents/workflow/task_agent_strategy.dart
  • lib/features/agents/workflow/task_agent_workflow.dart
  • lib/features/agents/workflow/task_source_renderer.dart
  • lib/features/agents/workflow/task_state_markdown.dart
  • lib/features/ai/functions/lotti_checklist_update_handler.dart
  • lib/features/ai/repository/ai_input_repository.dart
  • lib/features/settings/ui/pages/flags_page.dart
  • lib/l10n/app_cs.arb
  • lib/l10n/app_de.arb
  • lib/l10n/app_en.arb
  • lib/l10n/app_es.arb
  • lib/l10n/app_fr.arb
  • lib/l10n/app_localizations.dart
  • lib/l10n/app_localizations_cs.dart
  • lib/l10n/app_localizations_de.dart
  • lib/l10n/app_localizations_en.dart
  • lib/l10n/app_localizations_es.dart
  • lib/l10n/app_localizations_fr.dart
  • lib/l10n/app_localizations_ro.dart
  • lib/l10n/app_ro.arb
  • lib/utils/consts.dart
  • test/features/agents/database/agent_database_test.dart
  • test/features/agents/projection/compaction_summary_test.dart
  • test/features/agents/projection/decision_events_test.dart
  • test/features/agents/projection/input_events_test.dart
  • test/features/agents/projection/side_effect_key_test.dart
  • test/features/agents/service/agent_log_llm_summarizer_test.dart
  • test/features/agents/state/agent_providers_test.dart
  • test/features/agents/sync/agent_log_compactor_test.dart
  • test/features/agents/ui/agent_activity_log_test.dart
  • test/features/agents/workflow/change_set_builder_test.dart
  • test/features/agents/workflow/task_agent_strategy_test.dart
  • test/features/agents/workflow/task_agent_workflow_test.dart
  • test/features/agents/workflow/task_agent_workflow_test_helpers.dart
  • test/features/agents/workflow/task_source_renderer_test.dart
  • test/features/agents/workflow/task_state_markdown_test.dart
  • test/features/ai/functions/lotti_checklist_update_handler_test.dart
  • test/features/ai/repository/ai_input_repository_test.dart
  • test/helpers/fallbacks.dart
  • test/mocks/mocks.dart

Comment thread docs/implementation_plans/2026-06-02_executor_idempotency_fencing_plan.md Outdated
Comment thread lib/features/agents/projection/compaction_summary.dart
Comment thread lib/features/agents/projection/decision_events.dart Outdated
Comment thread lib/features/agents/README.md
Comment thread lib/features/agents/tools/agent_tool_registry.dart
Comment thread lib/features/agents/workflow/task_agent_workflow.dart Outdated
Comment thread test/features/agents/workflow/task_state_markdown_test.dart
@codecov

codecov Bot commented Jun 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.11%. Comparing base (daa46b0) to head (758d726).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff            @@
##             main    #3265    +/-   ##
========================================
  Coverage   99.11%   99.11%            
========================================
  Files        1196     1200     +4     
  Lines       96845    97165   +320     
========================================
+ Hits        95987    96306   +319     
- Misses        858      859     +1     
Flag Coverage Δ
glados 12.82% <30.29%> (+0.09%) ⬆️
standard 98.81% <97.30%> (-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 force-pushed the feat/agent_state_compaction branch from bb9c102 to 4f1f959 Compare June 4, 2026 18:02

@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.

♻️ Duplicate comments (1)
lib/features/agents/projection/compaction_summary.dart (1)

185-189: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Normalize body text to keep the promised single-line format.

rawBody.trim() preserves embedded newlines, so one captured entry can spill into multiple lines and break the compacted tail's line-oriented contract.

💡 Suggested fix
-  final body = rawBody.trim();
+  final body = rawBody.replaceAll(RegExp(r'\s+'), ' ').trim();
🤖 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/projection/compaction_summary.dart` around lines 185 -
189, The code builds rawBody/body which can contain embedded newlines and thus
violate the single-line compacted tail; update the normalization so rawBody
collapses all whitespace/newlines into a single space before trimming (e.g.
replace runs of whitespace like \s+ with a single space on rawBody) so that body
= normalizedRawBody.trim() yields a single-line string; apply this change around
the rawBody/body construction (referencing rawBody, body, and editedTag) so
entries never contain embedded newlines.
🤖 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.

Duplicate comments:
In `@lib/features/agents/projection/compaction_summary.dart`:
- Around line 185-189: The code builds rawBody/body which can contain embedded
newlines and thus violate the single-line compacted tail; update the
normalization so rawBody collapses all whitespace/newlines into a single space
before trimming (e.g. replace runs of whitespace like \s+ with a single space on
rawBody) so that body = normalizedRawBody.trim() yields a single-line string;
apply this change around the rawBody/body construction (referencing rawBody,
body, and editedTag) so entries never contain embedded newlines.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 724841ef-be15-40f4-a27f-6eef86b03409

📥 Commits

Reviewing files that changed from the base of the PR and between bb9c102 and 4f1f959.

📒 Files selected for processing (11)
  • docs/implementation_plans/2026-06-02_executor_idempotency_fencing_plan.md
  • lib/features/agents/README.md
  • lib/features/agents/projection/compaction_summary.dart
  • lib/features/agents/projection/decision_events.dart
  • lib/features/agents/projection/join_plan.dart
  • lib/features/agents/sync/agent_log_compactor.dart
  • lib/features/agents/workflow/task_agent_workflow.dart
  • test/database/database_test.dart
  • test/features/agents/projection/compaction_summary_test.dart
  • test/features/agents/projection/join_plan_test.dart
  • test/features/agents/sync/agent_log_compactor_test.dart
✅ Files skipped from review due to trivial changes (4)
  • lib/features/agents/projection/join_plan.dart
  • test/features/agents/projection/join_plan_test.dart
  • docs/implementation_plans/2026-06-02_executor_idempotency_fencing_plan.md
  • lib/features/agents/README.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • lib/features/agents/projection/decision_events.dart
  • lib/features/agents/sync/agent_log_compactor.dart
  • lib/features/agents/workflow/task_agent_workflow.dart

@matthiasn
matthiasn force-pushed the feat/agent_state_compaction branch from 4f1f959 to 20499ac Compare June 4, 2026 18:19
@matthiasn

Copy link
Copy Markdown
Owner Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces agent memory compaction for task agents, allowing older task-log entries to be summarized into a rolling memory while keeping recent entries verbatim. It also adds support for archiving duplicate checklist items, makes report updates conditional on material changes, and improves the agent conversation view. Database schema version 11 is introduced to allow multiple capture links to share a single payload digest. The review feedback suggests passing compactedTaskLog directly to _buildUserMessage to avoid mutable class-level fields, optimizing the invalidated check in selectActiveSummary via direct map lookups, and adding a null-assertion operator to loaded.event.inlineContent to prevent static analysis warnings.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread lib/features/agents/workflow/task_agent_workflow.dart
Comment thread lib/features/agents/projection/compaction_summary.dart
Comment thread lib/features/agents/sync/agent_log_compactor.dart

@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.

Caution

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

⚠️ Outside diff range comments (3)
test/features/agents/projection/compaction_summary_test.dart (1)

127-145: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Extend the oracle to validate the completeness check.

The property test's oracle (lines 128-132) only validates retraction invalidation, but the production selectActiveSummary also performs a completeness check: every non-suppressed event at or before the cutoff must be covered by coveredSources (context snippet 1, lines 105-110).

Currently the test passes log: _log(retractions: retractions) with no events: parameter, so events defaults to [] and the completeness check always passes. This means the oracle doesn't exercise that validation branch.

To achieve full property coverage, generate sample events alongside the retractions and extend the oracle's isValid predicate to mirror the production completeness logic:

final incomplete = log.events.any((event) {
  if (event.position.isAfter(s.cutoff!)) return false;
  final retractedAt = latestRetraction[event.contentEntryId];
  final suppressed = retractedAt != null && retractedAt.isAfter(event.position);
  if (suppressed) return false;
  return !s.coveredSources.containsKey(event.contentEntryId);
});
return !incomplete && /* existing retraction check */;

(The example tests at lines 236-292 already cover completeness scenarios, so this is a property-test-specific improvement.)

🤖 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/agents/projection/compaction_summary_test.dart` around lines
127 - 145, The oracle's isValid(SummaryCheckpoint s) only checks retractions but
ignores the completeness check performed by selectActiveSummary; update isValid
to also validate that every non-suppressed event at or before s.cutoff is
covered by s.coveredSources by inspecting log.events and latestRetraction: for
each event where event.position <= s.cutoff, skip if suppressed
(latestRetraction[event.contentEntryId] exists and isAfter(event.position)),
otherwise require s.coveredSources contains event.contentEntryId; only return
true when both the retraction check and this completeness check pass so the
property test exercises the same logic as selectActiveSummary.
test/features/agents/workflow/task_agent_workflow_test.dart (2)

819-831: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Return the requested digest as the cached payload id.

getEntity(digest) should hand back an entity whose id is that same digest. Returning 'sha256-v1:existing' here weakens the content-addressed contract and would still let a bad contentEntryId implementation pass.

Suggested fix
         when(
           () => mockAgentRepository.getEntity(
             any(that: startsWith('sha256-v1:')),
           ),
-        ).thenAnswer(
-          (_) async => AgentDomainEntity.agentMessagePayload(
-            id: 'sha256-v1:existing',
+        ).thenAnswer((invocation) async {
+          final digest = invocation.positionalArguments.single as String;
+          return AgentDomainEntity.agentMessagePayload(
+            id: digest,
             agentId: 'shared-input-content',
             createdAt: DateTime(2024),
             vectorClock: null,
             content: const {'role': 'system', 'text': 'cached'},
-          ),
-        );
+          );
+        });
🤖 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/agents/workflow/task_agent_workflow_test.dart` around lines 819
- 831, The test stub for mockAgentRepository.getEntity returns a hardcoded id
('sha256-v1:existing') instead of echoing the requested digest; update the
when(...).thenAnswer(...) to capture the incoming digest argument and use it as
the returned entity's id so the mocked content-addressing contract holds.
Specifically, change the thenAnswer closure for mockAgentRepository.getEntity to
read the digest from the invocation (e.g., invocation.positionalArguments.first
or invocation.positionalArguments[0]) and pass that value as the id to
AgentDomainEntity.agentMessagePayload (while keeping the other fields the same).

2784-2801: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

The “null id” linked-task case is no longer actually covered.

parseLinkedTasks() rewrites a missing id to 'linked-task', so the “Null ID Task” row becomes a valid lookup candidate instead of malformed input. That means this test only proves the empty-string case, not the missing-id branch it describes.

Also applies to: 4428-4458

🤖 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/agents/workflow/task_agent_workflow_test.dart` around lines
2784 - 2801, parseLinkedTasks currently replaces a missing id with the string
'linked-task', which hides the "null id" test case; change the id assignment to
preserve null by using id = row['id'] as String? (no default) and update the
title fallback to (row['title'] as String?) ?? id ?? 'linked-task' so
empty-string and missing-id cases are handled differently; apply the same change
to the other parseLinkedTasks occurrences that construct AiLinkedTaskContext to
ensure tests for null-id remain valid (refer to parseLinkedTasks and
AiLinkedTaskContext usages).
🧹 Nitpick comments (2)
lib/features/agents/README.md (1)

319-323: ⚡ Quick win

Consider clarifying "only capture runs" phrasing.

Line 322's phrase "with the flag off, only capture runs and the wake reads the journal as before" could be misread. The intent appears to be that capture always runs (building the event log in shadow), but when the flag is off, the wake ignores the captured events and reads the journal directly—so "only" scopes to "capture is the only new-system component active."

A reader might instead parse it as "nothing happens except capture," which obscures that the full legacy wake path still executes. Consider rephrasing for clarity, e.g., "with the flag off, capture still runs (building the event log in shadow) but the wake reads the journal as before—byte-identical behavior."

🤖 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/README.md` around lines 319 - 323, Reword the ambiguous
sentence that describes behavior when enable_agent_compaction is false: update
the sentence around "with the flag off, only capture runs and the wake reads the
journal as before" to explicitly state that capture still runs in shadow
(building the event log) while the legacy wake path continues to read the
journal unchanged; reference the flag name enable_agent_compaction and ADR
0017/ADR 0020 if helpful and produce a clarified line such as "with the flag
off, capture still runs (building the event log in shadow), but the wake reads
the journal as before — byte-identical behavior."
test/features/agents/workflow/task_agent_workflow_test.dart (1)

761-763: ⚡ Quick win

Assert the expected persisted entities instead of the total write count.

This already had to move once when prompt persistence added more writes. Another benign persistence change will break the test again without regressing the behavior under 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 `@test/features/agents/workflow/task_agent_workflow_test.dart` around lines 761
- 763, Replace the brittle numeric assertion on mockSyncService.upsertEntity
with assertions that the expected persisted entities (e.g., the system prompt
entity, the user message entity, and the state update entity) were upserted:
capture the arguments passed to mockSyncService.upsertEntity (using the mock's
capture mechanism or an ArgumentCaptor equivalent), then assert the captured
list contains entities with the expected identifiers/types/contents (inspect
properties such as role/messageId/payload) rather than asserting .called(5);
focus assertions on upsertEntity and the specific entity properties to make the
test resilient to unrelated persistence changes.
🤖 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.

Outside diff comments:
In `@test/features/agents/projection/compaction_summary_test.dart`:
- Around line 127-145: The oracle's isValid(SummaryCheckpoint s) only checks
retractions but ignores the completeness check performed by selectActiveSummary;
update isValid to also validate that every non-suppressed event at or before
s.cutoff is covered by s.coveredSources by inspecting log.events and
latestRetraction: for each event where event.position <= s.cutoff, skip if
suppressed (latestRetraction[event.contentEntryId] exists and
isAfter(event.position)), otherwise require s.coveredSources contains
event.contentEntryId; only return true when both the retraction check and this
completeness check pass so the property test exercises the same logic as
selectActiveSummary.

In `@test/features/agents/workflow/task_agent_workflow_test.dart`:
- Around line 819-831: The test stub for mockAgentRepository.getEntity returns a
hardcoded id ('sha256-v1:existing') instead of echoing the requested digest;
update the when(...).thenAnswer(...) to capture the incoming digest argument and
use it as the returned entity's id so the mocked content-addressing contract
holds. Specifically, change the thenAnswer closure for
mockAgentRepository.getEntity to read the digest from the invocation (e.g.,
invocation.positionalArguments.first or invocation.positionalArguments[0]) and
pass that value as the id to AgentDomainEntity.agentMessagePayload (while
keeping the other fields the same).
- Around line 2784-2801: parseLinkedTasks currently replaces a missing id with
the string 'linked-task', which hides the "null id" test case; change the id
assignment to preserve null by using id = row['id'] as String? (no default) and
update the title fallback to (row['title'] as String?) ?? id ?? 'linked-task' so
empty-string and missing-id cases are handled differently; apply the same change
to the other parseLinkedTasks occurrences that construct AiLinkedTaskContext to
ensure tests for null-id remain valid (refer to parseLinkedTasks and
AiLinkedTaskContext usages).

---

Nitpick comments:
In `@lib/features/agents/README.md`:
- Around line 319-323: Reword the ambiguous sentence that describes behavior
when enable_agent_compaction is false: update the sentence around "with the flag
off, only capture runs and the wake reads the journal as before" to explicitly
state that capture still runs in shadow (building the event log) while the
legacy wake path continues to read the journal unchanged; reference the flag
name enable_agent_compaction and ADR 0017/ADR 0020 if helpful and produce a
clarified line such as "with the flag off, capture still runs (building the
event log in shadow), but the wake reads the journal as before — byte-identical
behavior."

In `@test/features/agents/workflow/task_agent_workflow_test.dart`:
- Around line 761-763: Replace the brittle numeric assertion on
mockSyncService.upsertEntity with assertions that the expected persisted
entities (e.g., the system prompt entity, the user message entity, and the state
update entity) were upserted: capture the arguments passed to
mockSyncService.upsertEntity (using the mock's capture mechanism or an
ArgumentCaptor equivalent), then assert the captured list contains entities with
the expected identifiers/types/contents (inspect properties such as
role/messageId/payload) rather than asserting .called(5); focus assertions on
upsertEntity and the specific entity properties to make the test resilient to
unrelated persistence changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 71de289d-d577-44a4-87fc-5a6774a9a35e

📥 Commits

Reviewing files that changed from the base of the PR and between 4f1f959 and 20499ac.

📒 Files selected for processing (13)
  • docs/implementation_plans/2026-06-02_executor_idempotency_fencing_plan.md
  • lib/features/agents/README.md
  • lib/features/agents/projection/compaction_summary.dart
  • lib/features/agents/projection/decision_events.dart
  • lib/features/agents/projection/join_plan.dart
  • lib/features/agents/sync/agent_log_compactor.dart
  • lib/features/agents/workflow/task_agent_workflow.dart
  • test/database/database_test.dart
  • test/features/agents/projection/compaction_summary_test.dart
  • test/features/agents/projection/join_plan_test.dart
  • test/features/agents/sync/agent_log_compactor_test.dart
  • test/features/agents/workflow/task_agent_workflow_test.dart
  • test/features/agents/workflow/task_state_markdown_test.dart
✅ Files skipped from review due to trivial changes (3)
  • lib/features/agents/projection/join_plan.dart
  • test/features/agents/projection/join_plan_test.dart
  • docs/implementation_plans/2026-06-02_executor_idempotency_fencing_plan.md
🚧 Files skipped from review as they are similar to previous changes (7)
  • test/database/database_test.dart
  • lib/features/agents/projection/decision_events.dart
  • lib/features/agents/projection/compaction_summary.dart
  • test/features/agents/workflow/task_state_markdown_test.dart
  • test/features/agents/sync/agent_log_compactor_test.dart
  • lib/features/agents/sync/agent_log_compactor.dart
  • lib/features/agents/workflow/task_agent_workflow.dart

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