I replayed a four-event trace on paper before I trusted a side-lane retry classifier at all. The planner had already sealed compensation when the side lane finally returned its late yes answer. A naive merger treated that late yes as fresh permission to reopen the already sealed attempt. Would you really let one delayed label rewrite a closed attempt after compensation had already landed?
That order is the whole review, not a footnote I can patch later with a longer timeout. I am treating retry authorization as an append that must carry an open generation fence token. If the fence does not match the live attempt, the log rejects the label and leaves the seal untouched. Why would a slower lane earn more authority than the path that already closed the books?
Assumptions I will not smuggle in later
Before I trust the diagram, I want the constraints written where a reviewer can attack them. If any assumption is false in your system, stop and do not borrow the acceptance rule at the bottom. Would you rather discover a missing generation id during review, or after a sealed attempt has already been reopened?
- I assume delivery between planner, side lane, and decision log is at least once, so duplicates are normal rather than rare.
- I assume the side lane may run on a free server and call a free model, with no latency promise I will write down.
- I assume the primary path seals on its own clock and never waits unbounded for a classification to arrive.
- I assume each attempt has a monotonic generation, and a seal is terminal for that generation only.
I am not claiming a measured quota, a named model, or a permanent free tier, because those facts were not given to me. I also refuse to invent hardware, duration, or a benchmark just to make the side lane sound faster. What I will claim is narrower: MonkeyCode offers free model access and a free server option for this shadow lane.
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
I use that free model access only to draft advisory labels, and I host this desk harness on the free server option. A slow advisory label may inform the next generation, but it never becomes the component that seals the current one. If you remove every product name, the fence rule should still be worth keeping on your own bus.
The flow I would actually draw
planner --open(G)--> attempt_log
planner --classify(G)--> side_lane (free server + free model)
side_lane --label(G, yes|no|unknown)--> fence_gate
fence_gate --accept--> attempt_log only if G is open
fence_gate --reject--> stale_drop if G is sealed or unknown
planner --seal(G)--> attempt_log
Please read that diagram as an execution path, not as a loose slogan about separation of concerns. The fence gate is the only component allowed to interpret a side-lane label as an authorization. Everything else may propose a label, but nothing else may reopen a generation that has already been sealed. I would rather drop a late yes than let the diagram arrow skip the gate and write the log directly.
Numbered steps for one attempt
- Open generation G, persist the open mark, and only then emit the classify request carrying that same generation.
- Let the primary path proceed on its own clock, and never hold the seal timer hostage for a classification.
- On any arriving label, compare generation G with the log before you read yes, no, or unknown.
- If G is open and the label is yes, record one authorization; if G is sealed, append a stale drop and stop.
- Seal G from the primary path alone, and ignore every later label that still names that same generation.
Does step three feel redundant when you already trust the queue to preserve your original emit order? I do not trust that, because a restart can deliver the classify response after the seal message has landed. You emitted classify first, yet the log can still observe seal first, and that swap is the bug. If your mental model forbids that swap, your mental model is describing a different bus than mine.
Failure domains I refuse to collapse
The side lane, the free server process, and the decision log are three failure domains I will not merge. A free server restart can drop an in-flight classification while the planner retries the same request under one generation. That retry is a duplicate delivery, not a fresh attempt, so it must not mint a new authorization by accident. A late yes after seal is a stale append, and replaying it would reopen work the compensation already undid.
An unknown label is not a soft yes, because ambiguity must not widen retries once the denominator is closed attempts. What about a yes that arrives before the seal, gets accepted, and then a duplicate yes arrives after the seal? The first append stands, and the second must hit the stale drop, or your exactly-once story is only a hope. I would rather reject the duplicate than compensate again, because a second compensation can undo a valid retry.
A minimal simulator, labeled as unexecuted
I have not run this fixture against a production bus, so treat the script as a desk proposal you can execute locally. It encodes the fence, the seal, and the three outcomes I care about: accept, reject, and duplicate after seal. If the assertions fail, the invariant is already broken, and no model score can talk you out of that. Would you ship a merger that cannot pass this trace, just because the free lane usually sounds confident?
# proposal: fence_sim.py - not a production claim
from dataclasses import dataclass, field
@dataclass
class Attempt:
generation: int
sealed: bool = False
auths: int = 0
drops: int = 0
@dataclass
class Log:
attempts: dict = field(default_factory=dict)
def open(self, g: int) -> None:
self.attempts[g] = Attempt(g)
def seal(self, g: int) -> None:
self.attempts[g].sealed = True
def on_label(self, g: int, label: str) -> str:
attempt = self.attempts.get(g)
if attempt is None or attempt.sealed or label != 'yes':
if attempt:
attempt.drops += 1
return 'reject'
attempt.auths += 1
return 'accept'
def permute() -> None:
orders = [('label', 'seal'), ('seal', 'label'), ('label', 'seal', 'label')]
for order in orders:
log = Log()
log.open(7)
auths_at_seal = None
for step in order:
if step == 'label':
log.on_label(7, 'yes')
else:
auths_at_seal = log.attempts[7].auths
log.seal(7)
assert log.attempts[7].auths == auths_at_seal
print('fence held across injected orders')
if __name__ == '__main__':
permute()
python3 fence_sim.py
If you want a property check rather than one happy trace, permute seal and label before you argue about scale. Assert that a sealed generation never increases its auth count, even when the same yes is delivered twice. I would add that permutation to the harness before I discussed throughput, because one printed line is not an acceptance rule. The fixture still counts two yes labels before seal as two auths, so idempotent accept is the next change, not a hidden feature.
Acceptance rule and the denominator
The denominator is sealed generations that later receive at least one label, not the total number of model calls. The numerator is those same generations whose authorization count stays unchanged after the seal has been written. I accept this design only when that ratio stays at one across label-then-seal, seal-then-label, and duplicate-yes-after-seal. A free model that is often right does not move this ratio, because the fence is about order, not about label quality.
Tradeoffs I would not hide
| Choice | Latency | Correctness | Cost | What breaks |
|---|---|---|---|---|
| Wait for the side lane before seal | Higher | Stronger if the lane always returns | Free-lane calls sit on the critical path | A slow free server stalls every attempt |
| Seal without a fence | Lower | Weak | Cheap until a late yes reopens work | Compensation and retry both apply |
| Fence, reject stale, do not replay | Primary stays short | Seal is stable | Drops late labels | A useful late yes is ignored |
| Fence plus compensate on stale yes | Primary stays short | Risky | Extra writes | Second compensation undoes a valid retry |
I would pick the third row for the decision log, and I would keep label quality on a separate scoreboard. Waiting for the side lane looks safer until a slow free host stalls every attempt that was ready to seal. Sealing without a fence looks cheap until a late yes and a compensation both apply to the same generation. Do you actually need that late yes inside the sealed generation, or only as input when the planner opens the next one?
What I would change next
I would split the authorization store from the decision log so a side-lane bug cannot obtain the seal writer. I would stamp every classify request with generation, attempt id, and a hash of the tool error being judged. I would refuse any label whose hash does not match, even when the generation is still open and the label says yes. I would canary those three failure classes, delayed label, duplicated label, and restarted side lane, and halt if any sealed generation gains an auth.
A rejected late yes may open generation G plus one only through the planner, never through the fence gate itself. Free model access can draft those advisory labels, and the free server can host the harness, within the limits already stated. If you want to try that split, start from the fixture and let only the planner seal, while the free host only proposes labels.
Who should not use this
Skip this pattern if your product requires the classifier to block the seal, because I deliberately removed that wait. Skip it if you have no generation id, since a fence without a stable name is only a comment beside the diagram. Skip it if a human must approve every retry, because this gate only decides whether a label may append at all. I would not use a free lane as the only record of a retry, since I promise neither retention nor uptime.
Which order should break it?
Which event order breaks the invariant: seal, then a duplicate yes, then a planner restart that re-emits the same classify call? I would reject that yes, refuse to replay it into generation G, and refuse to compensate the sealed attempt again. Should your system do the same, or do you still believe a late yes is safer than a dropped label? If you cannot name that order, you do not yet have an invariant, only a hope that the side lane stays fast.
Top comments (1)
Official Platform Update
Security protocols have been updated for all developer accounts.