A resumed job re-sends the messages it already delivered #745

Closed
opened 2026-08-13 22:07:37 +00:00 by coilyco-ops · 0 comments
Member

RecordEffect and EffectApplied had no production caller, so job.Effects was empty for every job that ran and a re-execution repeated everything. That was harmless while a job's only output was a status line. It stopped being harmless when #356 and #742 gave a job ten ordered messages to say.

A job that emits ten messages and then resumes emits some of them twice, and the member reads the same paragraph again.

What ships

Each message records an effect keyed by position, content:N. A re-execution checks before sending and skips what was delivered.

The effect is recorded after the send, deliberately. Recording first would skip a message the origin never received, and a truncated answer is worse than a repeated paragraph. TestAFailedSendRecordsNoEffect holds that, and reversing the order fails it.

A replay still delivers what the first run did not reach, so a crash mid-answer is resumed rather than frozen.

What it does not do, which matters for reading this honestly

agent.go:311 builds a MemoryJobStore unless JobStoreDir is set, and deploy#464 records both lanes running the in-memory store today. So this guards in-process re-execution now, and nothing across a restart until deploy sets a directory. It is written to be inert rather than required: TestContentWorksWithoutAStore asserts the path still works with no store at all.

I would rather ship a guard that is honest about its reach than describe it as durable idempotency.

This closes two rows of 621, not four

RecordEffect and EffectApplied leave unwiredCapabilities. CommandFromPrompt and IsGrantDenial stay: those are still the deletion judgement #621 was filed for.

The guard another seat built caught the wiring and told me exactly what to do:

RecordEffect now has 1 production caller(s). sirens-echo#621 asks that wiring be recorded, so document it and drop this row

That is the repository working as designed, and it is the reason this change did not land with a stale table.

Verification

ward exec gate PASS on all six steps.

Two mutations, both compiling:

  • the replay guard never firing: fails TestAReplayedMessageIsNotSentTwice and TestAReplayStillDeliversWhatTheFirstRunDidNot
  • recording the effect before the send: fails TestAFailedSendRecordsNoEffect

closes #744

`RecordEffect` and `EffectApplied` had no production caller, so `job.Effects` was empty for every job that ran and a re-execution repeated everything. That was harmless while a job's only output was a status line. It stopped being harmless when [#356](https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/356) and [#742](https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/742) gave a job **ten ordered messages** to say. **A job that emits ten messages and then resumes emits some of them twice**, and the member reads the same paragraph again. ## What ships Each message records an effect keyed by position, `content:N`. A re-execution checks before sending and skips what was delivered. **The effect is recorded after the send, deliberately.** Recording first would skip a message the origin never received, and a truncated answer is worse than a repeated paragraph. `TestAFailedSendRecordsNoEffect` holds that, and reversing the order fails it. A replay still delivers what the first run did not reach, so a crash mid-answer is resumed rather than frozen. ## What it does not do, which matters for reading this honestly `agent.go:311` builds a `MemoryJobStore` unless `JobStoreDir` is set, and [deploy#464](https://forgejo.coilysiren.me/coilyco-bridge/deploy/issues/464) records both lanes running the in-memory store today. **So this guards in-process re-execution now, and nothing across a restart until deploy sets a directory.** It is written to be inert rather than required: `TestContentWorksWithoutAStore` asserts the path still works with no store at all. I would rather ship a guard that is honest about its reach than describe it as durable idempotency. ## This closes two rows of 621, not four `RecordEffect` and `EffectApplied` leave `unwiredCapabilities`. `CommandFromPrompt` and `IsGrantDenial` stay: those are still the deletion judgement [#621](https://forgejo.coilysiren.me/coilyco-gaming/sirens-echo/issues/621) was filed for. The guard another seat built caught the wiring and told me exactly what to do: > RecordEffect now has 1 production caller(s). sirens-echo#621 asks that wiring be recorded, so document it and drop this row That is the repository working as designed, and it is the reason this change did not land with a stale table. ## Verification `ward exec gate` PASS on all six steps. Two mutations, both compiling: - the replay guard never firing: fails `TestAReplayedMessageIsNotSentTwice` and `TestAReplayStillDeliversWhatTheFirstRunDidNot` - recording the effect before the send: fails `TestAFailedSendRecordsNoEffect` closes #744
Sign in to join this conversation.
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#745
No description provided.