Skip to content

feat(agent): say which forced source a queued run did not get - #842

Merged
CybotTM merged 1 commit into
mainfrom
feat/dropped-forced-source
Aug 20, 2026
Merged

feat(agent): say which forced source a queued run did not get#842
CybotTM merged 1 commit into
mainfrom
feat/dropped-forced-source

Conversation

@CybotTM

@CybotTM CybotTM commented Aug 20, 2026

Copy link
Copy Markdown
Member

Closes #809, implementing ADR-179 (#828).

The problem: switch a snippet or skill off between enqueue and start and the queued run loses it — that is ADR-175's rule and it stays. Nothing said so. The run did something other than what was asked for, and the only way to notice was to diff the queued request against the transcript.

What it does

The dequeue reports what it asked for and did not get, and why: deactivated for a record that is switched off, gone for one that no longer resolves.

Telling those apart is the whole decision — one is reversible, the other is not — and it needs the second lookup: a uid the enabled-only lookup skipped but the existence lookup finds is switched off. Without both, the two reasons collapse into one and the reader is told "dropped" without being told what to do about it.

It travels on RunAugmentation, is written through RunTrace as its own step kind before assembly, and reaches the run view through RunStep's payload and the timeline's allow-list.

Recorded on the resolve side, not inferred from the assembled messages — a source that never arrived leaves no mark in them, and that absence is precisely what an operator cannot see today.

Flattened to kind#uid reason, not counted. snippet#41 deactivated says what to do; 2 dropped flattens the distinction the record exists to make.

A run that dropped nothing records no step, so the step's presence is the signal — an empty step on every run is noise, and then nobody looks. The resume path is untouched: ADR-166 and ADR-175 keep a deactivated source resolving there, so nothing is dropped and a step would imply otherwise.

An existing test caught a real defect

ToolLoopServiceAssemblyOrderTest pins that the enabled-only lookup runs once per dequeue. My first version called it a second time — two identical queries on every dequeue. The records that did arrive are now passed in rather than re-queried: one extra query per kind instead of two. That assertion existed for a different reason and caught this for free.

Controls, each observed

Break Result
resolve both sides with the enabled-only lookup 2 tests fail — the reasons become indistinguishable
remove the empty-list guard the "clean run records no step" test fails

11 new tests: 4 on the step, 7 on the detection against real rows — including that a hidden but enabled source is not reported (the repositories ignore enable fields, so hidden is FormEngine visibility; reporting it would send an operator to fix something that is not broken), and that a deleted record reads gone rather than deactivated.

Two corrections to records, both mine

ADR-179 claimed RunAugmentation is @internal. It is on the frozen surface — api-surface.txt lists it with its full constructor. Corrected in the record rather than left for the next reader. The growth is additive (a parameter with a default plus one method) and announced under ### Added.

ADR-176 said "five builtins write through the DataHandler". ADR-180 added a sixth within a day of that record being written. The number is replaced by the command that answers it — grep -l 'use WritesThroughDataHandlerTrait' …, the trait every writer uses, rather than a search for the class name, which also matches the two traits themselves and overcounts by two. I wrote that wrong anchor first and checked it before trusting it.

Gates: phpstan level 10 clean, unit 7267 pass, fuzzy pass, functional -d sqlite on the touched classes pass, cgl and rector -n stable at PHP 8.2.

Closes #809

ADR-175 settled that a source switched off between enqueue and start
does not enter the run. Nothing said so. The run proceeded without it and
the only way to notice was to compare the queued request against the
transcript.

The dequeue now reports what it asked for and did not get, and why:
deactivated for a record that is switched off, gone for one that no
longer resolves. Telling those apart is the point — one is reversible,
the other is not — and it needs the SECOND lookup: a uid the enabled-only
lookup skipped but the existence lookup finds is switched off. Without
both, the two reasons are one.

It travels on RunAugmentation, is written through RunTrace as its own
step kind before assembly, and reaches the run view through RunStep's
payload and the timeline's allow-list. Recorded on the resolve side
rather than inferred from the assembled messages, because a source that
never arrived leaves no mark in them — that absence is what an operator
cannot see today.

Flattened to "kind#uid reason" strings, not a count: "2 dropped" would
flatten exactly the distinction the record exists to make.

A run that dropped nothing records no step, so the step's presence is the
signal. The resume path is untouched: ADR-166 and ADR-175 keep a
deactivated source resolving there, so nothing is dropped and a step
would imply otherwise.

An existing test caught a real defect in the first version.
ToolLoopServiceAssemblyOrderTest pins that the enabled-only lookup runs
once per dequeue; my detection called it a second time. The records that
did arrive are now passed in rather than re-queried — one extra query per
kind instead of two.

Two controls, each observed: resolving both sides with the enabled-only
lookup makes the two reasons indistinguishable and fails two tests;
removing the empty-list guard makes a clean run record a step and fails
the test that forbids it.

ADR-179 claimed RunAugmentation was @internal. It is on the frozen
surface, and that sentence is corrected in the record rather than left
for the next reader. The growth is additive: a parameter with a default
plus one method, announced under ### Added.

ADR-176 said five builtins write through the DataHandler. ADR-180 added a
sixth within a day, so the number is replaced by the command that answers
it — the trait every writer uses, not a search for the class name, which
also matches the two traits and overcounts by two.

Closes #809

Signed-off-by: Sebastian Mendel <sebastian.mendel@netresearch.de>
@CybotTM
CybotTM requested a review from a team as a code owner August 20, 2026 15:46
Copilot AI lite review requested due to automatic review settings August 20, 2026 15:46
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests Test-related changes labels Aug 20, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

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

Automated approval for maintainer PR

All automated quality gates passed. See SECURITY_CONTROLS.md for compensating controls.

@sonarqubecloud

Copy link
Copy Markdown

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@codecov

codecov Bot commented Aug 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.00000% with 15 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.64%. Comparing base (cb7e823) to head (137c496).
⚠️ Report is 10 commits behind head on main.

Files with missing lines Patch % Lines
Classes/Service/Agent/AgentRunRequestCodec.php 69.76% 13 Missing ⚠️
Classes/Domain/ValueObject/DroppedSource.php 0.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##               main     #842      +/-   ##
============================================
- Coverage     86.74%   86.64%   -0.10%     
- Complexity     9865     9896      +31     
============================================
  Files           573      574       +1     
  Lines         31920    32037     +117     
============================================
+ Hits          27689    27760      +71     
- Misses         4231     4277      +46     
Flag Coverage Δ
unit 65.11% <75.00%> (+0.04%) ⬆️

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

Files with missing lines Coverage Δ
Classes/Domain/ValueObject/RunStep.php 97.29% <100.00%> (+0.32%) ⬆️
...sses/Service/Agent/Timeline/RunTimelineFactory.php 89.71% <ø> (ø)
Classes/Service/Tool/RunAugmentation.php 0.00% <ø> (ø)
Classes/Service/Tool/RunTrace.php 96.62% <100.00%> (+0.37%) ⬆️
Classes/Service/Tool/ToolLoopService.php 97.32% <100.00%> (+0.01%) ⬆️
Classes/Domain/ValueObject/DroppedSource.php 0.00% <0.00%> (ø)
Classes/Service/Agent/AgentRunRequestCodec.php 86.82% <69.76%> (-8.64%) ⬇️

... and 1 file with indirect coverage changes

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

@CybotTM

CybotTM commented Aug 20, 2026

Copy link
Copy Markdown
Member Author

Review record — no Copilot review exists (monthly, account-wide quota), so the green gate is not a read of the change.

An existing test found a real defect in my first version. ToolLoopServiceAssemblyOrderTest pins that the enabled-only lookup runs once per dequeue; my detection called it a second time, doubling that query on every queued run. The records that did arrive are now passed in rather than re-queried. That assertion exists for a different reason and caught this for free — which is the argument for pinning query counts at all.

Both new assertions were seen to fail, not just seen to pass: resolving both sides with the enabled-only lookup makes the two reasons indistinguishable and fails two tests; removing the empty-list guard makes a clean run record a step and fails the test that forbids it.

Two of the seven detection tests are the ones I would not have written from the ADR alone. A hidden but enabled source is not reported — the repositories ignore enable fields, so hidden is FormEngine visibility, and reporting it would send an operator to fix something that is not broken. And a deleted record reads gone, not deactivated, because both lookups keep the deleted restriction; that is the only reason the two reasons stay separable at all.

Two corrections to records, both mine, both found by opening the file rather than trusting the sentence. ADR-179 claimed RunAugmentation is @internal; api-surface.txt lists it with its full constructor. ADR-176 said five builtins write through the DataHandler; ADR-180 added a sixth within a day, so the number is replaced by the command that answers it — and my first anchor for that returned 8, because the two traits mention the class name too. I checked the anchor before trusting it.

Deliberately not flattened to a count. snippet#41 deactivated says what to do about it; 2 dropped erases the distinction the record exists to make.

Gates: phpstan level 10 clean, unit 7267 pass, fuzzy pass, functional -d sqlite on the touched classes pass, cgl and rector -n stable at PHP 8.2.

Assisted by claude-code:claude-opus-5 — Session

@CybotTM

CybotTM commented Aug 20, 2026

Copy link
Copy Markdown
Member Author

Self-review: 137c496

The review this pull request demands is unsatisfiable (Copilot quota wall or repeated bot failures on this head). Per the documented fallback, the diff on this head was reviewed by the PR author; this comment is the on-the-record attestation the merge gate reads back. It stops matching on the next push.

@CybotTM
CybotTM added this pull request to the merge queue Aug 20, 2026
Merged via the queue into main with commit 1c46f74 Aug 20, 2026
95 of 96 checks passed
@CybotTM
CybotTM deleted the feat/dropped-forced-source branch August 20, 2026 18:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation tests Test-related changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A forced source dropped before a run starts is silent

2 participants