feat(telemetry): name the check that refused a reply, at both layers #666

Merged
coilyco-ops merged 1 commit from coilyco-ops/name-the-check-that-refused into main 2026-08-13 18:21:36 +00:00 AGit
Member

closes #652 criteria 2 and 3. The settle span landed separately as PR 656 while this was in the lane, so this carries only what that does not: the refusing check at both layers, and a delivery record.

closes #652 criteria 2 and 3. The settle span landed separately as PR 656 while this was in the lane, so this carries only what that does not: the refusing check at both layers, and a delivery record.
feat(telemetry): name the check that refused a reply, at both layers
All checks were successful
ci / image-build (pull_request) Successful in 23s
ci / test (pull_request) Successful in 37s
ci / publish-echo-image (pull_request) Has been skipped
ci / publish-observed (pull_request) Has been skipped
2d2390387e
A rejected reply was attributable to the model and not to the rule that refused
it. response.validate now carries response.check, naming the check, or none when
the reply passed. Present on every turn, because absence of an attribute is not
something a reader should have to interpret.

The checks became a slice rather than a chain of if err == nil conditions, since
their order is the contract and a chain cannot report which link refused. No
check changed and none moved.

The reply checks are not the only place a reply is rejected. The completion
layer runs its own contract check first, on parse and response style, and that
is the one that ends a turn as model_failed. model.response.repair now carries
the contract error that triggered it, and model.response.refused records the
reason a turn gave up rather than repairing again.

Found by replaying sirens-echo#651's two verbatim rejected answers offline. Both
pass all seven reply checks. Both are refused by ValidateResponseStyle under the
neutral profile, on first-person voice, because they open "No, I do not have
access" and continue "My available tools are". That is the natural English
phrasing of a capability answer, so under neutral a member asking Echo what it
can do is close to guaranteed to hit it twice and be told the backend is down.

TestARepairRecordsWhatItRefused reproduces that end to end against a stub proxy,
so the case survives the rule moving.

turn.reply.delivered is emitted when a send returns. Only the failure was
recorded before, so delivery had to be inferred from the absence of an error,
which forced a retraction on sirens-echo#619.

The settle span is not here. Pull request 656 landed it as turn.progress.settle
while this was in the lane, carrying the beat as well as the hold, and the doc
points at theirs. This carries only what that change does not.

No verdict changes. Whether neutral style should permit first person in a
capability answer belongs to 651 and sirens-echo#396.

closes #652

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: Kai Siren <coilysiren@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
coilyco-gaming/sirens-echo!666
No description provided.