---
title: "The Unsent Thunderstorm · Agentic Atlas"
description: "The Unsent Thunderstorm: one rejected weather bulletin shows when to discard, retry, undo, or stop before acting."
canonical: "https://agentic-atlas.dev/nodes/unsent-thunderstorm"
last-updated: "2026-08-19"
---

[← the survey](https://agentic-atlas.dev/atlas)

1. example of [Effect Discipline](https://agentic-atlas.dev/nodes/effect-discipline)
   The Unsent Thunderstorm
   Hook
   one rejected weather bulletin shows when to discard, retry, undo, or stop before acting
   Thesis
   Reject handles the answer and effect discipline handles what the answer already did, so a rejected run's legal recovery (discard, retry, undo, or stop) is decided by what was arranged before the action, not by a generic retry-on-failure rule.
   Laws & fences
  - When validation runs last, throwing away the rejected answer changes nothing to its left: the duplicate preview remains, the invalid page is live, and the forecast has already gone out.
  - The five outcomes are different permissions rather than stronger and weaker versions of one: the keyed preview may remain, the staged draft may be discarded, the live page must be restored, and the subscriber forecast must never be sent before the check.
  - Idempotence protects the retry, not the discard; the keyed preview still remains after rejection, while isolation makes discard legal because the candidate was never promoted.
  - When no compensator is in the contract, the check must precede authority to act; for the append-only forecast log the legal move is a gate, not a more elaborate apology after releasing indoor thunder.
   When to reach
  - Reach for it when asking what reject and retry mean once an agent can do more than return text, such as updating a preview, publishing a live page, or sending to subscribers, since those actions survive the rejected draft.
  - Reach for it past the release furniture, which is incidental: the same distinctions govern database upserts, generated artifacts, deployment APIs, shared state, and outbound messages.
  - Skip it as proof that real message delivery is irreversible in every system; the append-only log is a local stand-in, and what the fixture proves is the placement decision.
   Provenance
   [unsent-thunderstorm/lessons](https://agentic-atlas.dev/nodes/unsent-thunderstorm#lessons) · v1.0.2
   Addresses
   atlas_cards unsent-thunderstorm
2. [
   Effect Discipline
   ](https://agentic-atlas.dev/nodes/effect-discipline)

The card, in place · its connections drawn edges from atlas_links unsent-thunderstorm

On this plate

[context](https://agentic-atlas.dev#context)[problem-signal](https://agentic-atlas.dev#problem-signal)[choice](https://agentic-atlas.dev#choice)[before](https://agentic-atlas.dev#before)[implementation](https://agentic-atlas.dev#implementation)[result](https://agentic-atlas.dev#result)[verification](https://agentic-atlas.dev#verification)[lessons](https://agentic-atlas.dev#lessons) [relationships](https://agentic-atlas.dev#relationships)

Every section is addressable on its own. Read only the ground you need.

## Context

[](https://agentic-atlas.dev#context)

context.md

This example explains one problem: **what does “reject and retry” mean after an agent has already changed the world?**

Pocket Weather sells forecasts in jars to people who find the sky needlessly large. An agent writes its release bulletin; a validator rejects the draft because it still contains a `TODO`:

```
# Pocket Weather 1.4

TODO: confirm the indoor thunder limit.
```

If the agent only returned text, the workflow could throw the draft away and try again. But the task also lets the agent update a preview, publish the live page, and send the forecast to subscribers. Those actions survive after the draft is rejected.

The same invalid draft is used in every arm below. Only the way one action is prepared changes, so each recovery difference has one cause.

## Problem signal

[](https://agentic-atlas.dev#problem-signal)

problem-signal.md

The unsafe baseline checks last:

```
agent ──▶ write preview ──▶ publish page ──▶ send forecast ──▶ validate
                                                                      │
                                                                      └── REJECT
```

At `REJECT`, throwing away the answer changes none of the things to its left. The duplicate preview remains, the invalid page is live, and the forecast has already gone out. Discarding the text does not put the thunder back in its jar.

## Choice

[](https://agentic-atlas.dev#choice)

choice.md

Before granting each action, decide what must happen if validation later says no:

| Arrange this before the action | The rejected run can now… | Class name |
| --- | --- | --- |
| Give the agent no write authority | Be thrown away; nothing happened | Pure / read-only |
| Update one stable preview key | Run again without creating another preview | Idempotent |
| Write only to a private stage | Have its whole stage thrown away | Isolated |
| Save the exact page being replaced | Restore that page, then try again | Reversible-at-a-cost |
| Validate before sending | Be stopped before an action that cannot be undone | Irreversible |

The class name is shorthand for the recovery in the middle column. A generic “retry on failure” rule cannot replace this decision: retry is safe for the keyed preview, but it cannot unsend a forecast.

## Before

[](https://agentic-atlas.dev#before)

before.md

[`demo.py`](demo.py) makes the unsafe baseline concrete. After the invalid draft is rejected, four facts remain in its temporary world: a draft file, two preview entries, an invalid live page, and one sent forecast. The baseline has no single cleanup action because those effects do not share a recovery shape.

## Implementation

[](https://agentic-atlas.dev#implementation)

implementation.md

[`demo.py`](demo.py) first reproduces the raw baseline, then starts each shifted arm in a freshly seeded world with the same candidate. Each arm changes one boundary decision.

### Shift 1 — return a plan, do not act *(pure)*

`describe_only()` returns the intended operations without executing them. The fixture snapshots every file before and after the call and asserts byte-for-byte equality. The [dispatch](https://agentic-atlas.dev/glossary/dispatch) is pure, so rejection licenses **discard**.

### Shift 2 — make repeated previews converge *(idempotent)*

`upsert_preview()` replaces the value at `pocket-weather-1.4`. Calling it twice leaves one preview, which makes **retry** free. The preview remains after rejection — idempotence protects the retry, not the discard.

### Shift 3 — keep the draft off the live page *(isolated)*

`write_isolated()` writes under `staging/pocket-weather-1.4/`. Validation rejects the candidate, the stage is removed, and the live page retains its original bytes. Isolation makes **discard** legal because the candidate was never promoted.

### Shift 4 — save what the publish replaces *(reversible-at-a-cost)*

`publish_with_snapshot()` is still a raw live write, but its footprint is one named file and its exact prior bytes are captured first. Rejection invokes `compensate_release()`, which restores those bytes. The effect is reversible-at-a-cost; **compensation** pays for the discard.

### Shift 5 — check before sending *(irreversible)*

The append-only forecast log has no compensator. The workflow checks the bulletin and confirmation before it calls `send_forecast()`. The invalid payload therefore never reaches the irreversible boundary. The legal move is a **[gate](https://agentic-atlas.dev/glossary/gate)**, not a more elaborate apology after releasing indoor thunder.

## Result

[](https://agentic-atlas.dev#result)

result.md

One rejected draft now produces five different, checkable outcomes:

```
baseline              ──▶ REJECT   every raw effect remains
pure                  ──▶ DISCARD  nothing happened
idempotent            ──▶ RETRY    one keyed preview remains
isolated              ──▶ DISCARD  private stage removed
reversible-at-a-cost  ──▶ UNDO (compensate)  exact live page restored
irreversible          ──▶ STOP (gate)        forecast never sent
```

These are different permissions, not stronger and weaker versions of one permission. The preview may remain because retrying is safe. The staged draft may be discarded because it never became live. The live page must be restored. The subscriber forecast must never be sent before the check.

## Verification

[](https://agentic-atlas.dev#verification)

verification.md

Run the example from its directory:

```
python3 demo.py
```

The script uses only the Python standard library and writes only inside temporary directories. It first asserts that the rejected baseline leaves its draft, duplicate previews, live-page mutation, and sent-forecast entry behind. The five independent arms then assert that description is byte-preserving, duplicate preview retries converge to one keyed record, rejected staged work never reaches the live page, compensation restores the exact pre-dispatch bytes, and the gated bulletin sends no forecast.

The append-only log is a local stand-in for an irreversible effect; the fixture does not prove that real message delivery is irreversible in every system. It proves the placement decision: when no compensator is in the [contract](https://agentic-atlas.dev/glossary/contract), the check must precede authority to act.

## Lessons

[](https://agentic-atlas.dev#lessons)

lessons.md

**Decide how failure recovers before uncorking the weather.** “Reject” handles the answer; effect discipline handles what the answer already did. It changes what the workflow prepares up front: no authority, a stable key, a private stage, an exact undo, or a check before action.

The release furniture is incidental. The same distinctions govern database upserts, generated artifacts, deployment APIs, shared state, and outbound messages. A rejected [return](https://agentic-atlas.dev/glossary/return) tells you *that* recovery is needed; the effect class tells you which recovery is still legal.

The relationships ledger

Evidence-bearing references

## Relationships

Every connection keeps the section where it was found. The map above orients; this ledger carries the evidence.

### Outbound references 0

1. No outbound references.

### Inbound references 1

1. in-slice · occurrence 1
   [Effect Discipline](https://agentic-atlas.dev/nodes/effect-discipline#evidence)
   one rejected weather bulletin shows when to discard, retry, undo, or stop before acting
   Evidence: [Evidence](https://agentic-atlas.dev/nodes/effect-discipline#evidence) · occurrence 1

[↑ back to the top](https://agentic-atlas.dev#content) [← the survey](https://agentic-atlas.dev/atlas)

Node unsent-thunderstorm · corpus 31de4cb · Catalog revision 35263c4c415da742953d0462804fb14424e2244dae4c63efd27e468988de70ab