Decision Packet
Version 1.5.0 (2026-09-20)
An agent that has analysed N items needs N decisions from its principal. Every default shape
fails at scale, and the measurement that produced this skill is blunt: 26 rounds of per-item
conversation produced 0 of 30 decisions. One packet produced 30 of 30, in one pass, in one
paste.
| Shape |
Why it fails |
| One question per turn |
N round-trips. This is what cost 26 rounds. |
| One long prose report |
The principal holds thirty judgments in their head, keeps their place, and composes a reply that re-identifies each item. |
| A table in chat |
Readable, not operable. Nowhere to record a decision, no state if they stop halfway. |
| A form that submits somewhere |
Needs a backend, and the reply still has to get back to the agent. |
The failure is not that the principal lacks information. The medium collects no structure, so
the burden of structuring the response falls on the person, every time, for every item.
The property to preserve above all others: the principal's cost scales with the number of
sittings, not the number of items.
When to use it
Load when you owe your principal five or more parallel decisions of the same shape, each
needing supporting context, where you have a defensible recommendation per item and the decisions
matter enough to deserve attention but are too numerous for per-item conversation.
Natural fits: review findings to fix or waive · dependency bumps to take or hold · drafts to
publish, edit, or kill · files to migrate or leave · flaky tests to quarantine or fix · features
to build now, later, or never · candidates to advance on a defined rubric.
When NOT to use it
- Fewer than about five decisions. Just ask in chat. The generator refuses below five without
--allow-small.
- The decisions are not parallel in shape. A packet of unlike questions is a form, and a form
is worse than a conversation.
- You have no recommendation per item. Then the packet is a questionnaire and your analysis
is not finished. Do the analysis. The generator refuses a packet where no row carries a
recommendation.
The failure mode of a good pattern is over-application. These three limits are the skill.
The six load-bearing properties
Requirements, not suggestions. Each is why it worked.
- Self-contained rows. Item, recommendation, reasoning, and a link to the underlying
artifact. The principal never leaves the packet to decide.
- The recommendation is marked on the control, not merely stated in prose. Agreeing costs one
click. The agent's judgment does work instead of being described.
It is deliberately not pre-selected — a packet that opens fully decided cannot distinguish
"I agreed" from "I never looked", and would report decisions nobody made.
- A free-text box on every row, beside the buttons. Never force a principal into your option
set. In the origin run one such note, on one row, carried information no button could have.
- Local persistence keyed per item. Thirty decisions is more than one sitting for anyone doing
it properly. Storage access is guarded: where a browser blocks it the packet still works and
says so in the summary.
- A paste-able summary the tool generates. This is the property that closes the loop. The
structure you need is produced by the packet, not composed by the person.
- Filed in the owning project, never only as a chat artifact. The spec, the generated page,
and the rulings the principal pastes back live in the owning synthesis project's
resources/artifacts/ as <date>-<slug>-spec.json, <date>-<slug>.html, and
<date>-<slug>-rulings.json. A packet that exists only as a published Claude artifact is
readable by one client in one conversation; the project directory is readable by every agent
working the project, ChatGPT Codex included, and by the next session after this one is gone.
build_packet.py --file-into files the first two; record_rulings.py files the third.
Content requirements, which matter as much as the mechanics
- Name filters from the content, not from generic severity. "Needs a fix", "We disagreed",
"Ready as written", "Not yet decided" — so the principal picks their own path through the set
rather than going 1 to N.
- Surface disagreement; never converge before the principal sees it. Eight rows in the origin
run showed two reviewers' conflicting verdicts and the principal broke all eight ties.
Converging first would have shown a false consensus. Use the
disagreement block.
- A summary band before the detail, so the shape of the work reads in three seconds.
- Severity in form as well as words — the coloured rail per row is how the eye finds exceptions
while scrolling.
- Recommend against your own prior work where that is true, including failed hypotheses. A
packet that only argues one way is a sales document, and the buttons stop being trusted.
The reader contract (v1.1.0) — comprehension is a load-bearing property
The origin measurement has a dark twin, measured on the same principal on
2026-08-29: a 15-row packet written in project-internal language collected
0 of 15 decisions. Every mechanical property above worked — filters,
persistence, marked recommendations — and none of it mattered, because the
rows named things only the authoring session knew ("C1", "holdout",
"quarantine", "protected strata"). The principal's verdict: "written in some
alien or machine language." Structure without comprehension collects
nothing. The packets that ran 30/30 were about things the principal already
knew — articles, titles, links.
So a packet is a stranger-read document, and authoring one starts where
synthesis-reader-briefing starts: who reads this, what do they bring, what
does it ask, what do they leave with. Then, per row:
- The label is plain language. Internal IDs may appear as chips; they
are never the name.
- Context says what the thing IS in words the reader already has,
before any result about it.
- An
impact block states consequences, both ways — what actually
happens if they take the recommendation and if they don't, in outcomes
the principal cares about (what ships, what it costs, what dies), never
in internal treatment vocabulary. The generator renders it distinctly.
- Options are labeled by what pressing them does, never by a bare
acknowledgement. Measured on 2026-09-14: on a 9-row packet, three rows
offering "Yes, do that" / "No" collected notes instead of decisions — the
principal could not tell what each button would do to the thing in
question, so the note boxes carried what the buttons should have. "Keep
them on my phone" / "Take them off my phone" is the accepted form. The generator raises a
READER finding for a bare label (yes, no, ok, okay, cancel, accept,
decline, approve, reject, do it, go, stop, and their "Yes, do that" /
"No thanks" forms, compared after trimming punctuation and case, and any
of these padded with a stopword: "Accept it", "Approve this", "Do that")
and for two labels in one option set that do not differ in a content word;
--strict-reader refuses to build, naming the row, the labels, and the
accepted form.
- The consequence sits on the button. Each option button carries, under
its label, what pressing it does: the option's own
consequence when the
spec gives one, otherwise impact.accept under the recommended option and
impact.decline under every other one. The row's impact block stays as
the row's summary; the text on the button is what the principal reads at
the moment of choosing.
- Every surviving term of art gets a one-clause gloss — inline on first
use, or in the packet-level
glossary band.
audience names the reader. One sentence. If you cannot write it,
you do not know who the packet is for, and neither will they.
--strict-reader makes the generator refuse a packet missing audience or
per-row impact, or carrying option labels that name no consequence. Use it
for every packet handed to a principal. The warnings print either way;
strictness is the difference between a warning you read and a packet they
cannot.
Use
python3 scripts/build_packet.py --schema # the spec format
python3 scripts/build_packet.py spec.json -o packet.html --strict-reader
python3 scripts/build_packet.py spec.json --stdout # to a pipe
python3 scripts/build_packet.py spec.json --strict-reader \
--file-into PROJECT/resources/artifacts/ # + <date>-<slug>-spec.json, <date>-<slug>.html
python3 scripts/record_rulings.py paste.txt \
--file-into PROJECT/resources/artifacts/ # -> <date>-<slug>-rulings.json
Write a JSON spec, generate, file, hand over the file. It is self-contained: no build step, no
dependencies, no server. It opens from disk, over a local HTTP server, or published as an
artifact, in light or dark, on a phone or a laptop.
File it, then hand it over. --file-into DIR writes a dated copy of the spec and of the page
into DIR after a successful build. DIR is the owning project's resources/artifacts/ and must
already exist: the generator refuses a missing directory rather than creating one where you did
not mean. --date YYYY-MM-DD sets the date in the names (default: today). Publish the page as
an artifact too if that is how the principal will open it; the filed copy is the one other
agents read.
Record what came back. When the principal pastes the summary, save the paste to a file and
run record_rulings.py paste.txt --file-into DIR. It parses the exact text the packet's "Copy
summary" button emits and writes <date>-<slug>-rulings.json beside the spec and page: packet,
ruled_on, decided, total, and one ruling per row with id, label, choice_label,
took_recommendation (true, false for an override, null when undecided or when the row carried
no recommendation), accepted_in_bulk (the packet keeps bulk acceptance distinct from a
considered click, and so does the file), recommended_label, and note. It refuses text that
is not in that format, naming the line that failed, the form expected there and the text it
received; it reads a whitespace-only line as blank, since chat surfaces pad empty lines; it
refuses a paste whose row count or decided count disagrees with its own Decided n of m. line;
and it keeps an existing rulings file for the same date and packet unless --replace is passed.
Commit all three files with the project.
Enforcement (v1.5.0) — generator output is verified, not trusted
Prose above tells the agent to generate; this section is what happens when
one freelances instead. Every page build_packet.py emits carries a
provenance marker pinning its embedded spec (synthesis-decision-packet spec-sha256). record_rulings.py refuses a paste whose packet has no
filed -spec.json. And the context doctor's skill-outputs check fails
any packet page under a project's resources/artifacts/ that is not
verifiable generator output: unmarked with no filed rulings is a defect
(rebuild with the generator or remove it); a marker that disagrees with
the embedded spec is a defect (never hand-edit generator output); a
closed record (unmarked but ruled) warns. The rule ships on every
machine with install, upgrade, and doctor — it is not a local note.
Generate from a data array; never hand-author rows. Thirty hand-written blocks drift. One
array with a render loop cannot. That is the whole reason this is a generator rather than a
template.
The generator validates before it emits and refuses to build a broken packet: duplicate ids
(they key persistence), ids with leading, trailing or doubled whitespace or a line break (the
summary line is id label, split on its first double space), a line break in the title, a row
label or an option label (the paste is one line per field, and a packet that builds must file),
an option set with fewer than two options (a one-button set records no decision), two options
in one set sharing a value or a value that is not a non-empty string (the value keys the pressed
state and the summary label through a DOM dataset, which stores strings), a recommendation
outside its own option set, a packet with no recommendations at all, malformed option sets at
the packet level or on a row, malformed disagreement, impact, or glossary blocks.
Two defects that are permanent fixtures
Both shipped in the reference implementation; one reached the principal in real use. They are
regression-tested in scripts/test_build_packet.py.
- Charset in the first bytes. Without
<meta charset="utf-8"> ahead of everything, typographic
punctuation renders as mojibake when served over a plain local HTTP server. Found by loading
the page, not by reading the source — which is why the fixture asserts on bytes and why you
should always open a generated packet before handing it over.
- The copy control must never fail silently.
navigator.clipboard.writeText is blocked inside
a sandboxed artifact iframe with no clipboard-write permission; in the origin run the button
did nothing and said nothing, and the principal worked around it by hand. The required order:
select the textarea first so a manual ⌘C/Ctrl+C always works, say something synchronously,
then document.execCommand("copy") (which does work in sandboxed iframes), then the async API,
and report honestly which path succeeded. A third fixture was added on 2026-08-28 after driving
the real button surfaced an empty status line while the async promise was unsettled — an
unbounded silent interval is the same defect as a permanent one.
Relationship to other skills
synthesis-autopilot should call this, not reimplement it. Autopilot requires "batched
questions for the user"; this is the concrete artifact that requirement was missing. A
round-trip budget only means something if one round-trip can carry many decisions.
- The adversarial review family — this is where an engagement surfaces its unresolved
disagreements. Pair it with a status for findings that are not open, not conceded, and not the
agents' to close.
- The handoff queue (
synthesis-project-management/scripts/handoff.py) moves work between
agents. The decision packet moves decisions between agent and principal. Together they are
the two directions that stop routing everything through a person as the transport layer.
Changelog
- 1.5.0 (2026-09-20) — Enforcement: the generator emits a provenance marker pinning the embedded spec;
record_rulings.py refuses pastes with no filed -spec.json; the context doctor's skill-outputs check fails unmarked live packets as defects. After a session hand-authored two packets.
- 1.4.0 (2026-09-14) — Option labels must name consequences: READER findings for bare
acknowledgements and for two labels that do not differ in a content word, fatal under
--strict-reader, with the row, the labels, and the accepted form in the message. Each option
button carries its consequence under the label (consequence per option, else the row's
impact mapped onto the buttons). Sixth load-bearing property: packets and rulings are filed
in the owning project's resources/artifacts/ — --file-into, --date, and the new
record_rulings.py, whose parser is pinned to the summary format the page emits. Per-row
option sets are validated like the packet-level set; ids with edge or doubled whitespace are
refused. The worked example's options now name outcomes. Repair round the same day: a line
break in the title, a row label, an option label or an id is refused, so a packet that builds
can always be filed; a one-button option set, two options sharing a value, and a non-string
value are refused; an acknowledgement padded with a stopword ("Accept it", "Do that") is
caught; record_rulings.py counts the title underline in UTF-16 units as the page does,
reads whitespace-only lines as blank, and every refusal quotes the text it received; the
worked example's paste and rulings file match its two-row spec, and a fixture holds them to
the parser.
- 1.3.0 (2026-08-29) — Ids must be non-empty strings: JSON
1 and "1" become the same
localStorage key, so JSON-distinct ids could share saved state.
- 1.2.0 (2026-08-29) — The relationship section names the handoff queue as the
agent-to-agent transport.
- 1.1.0 (2026-08-29) — The reader contract:
audience, glossary, per-row impact, and
--strict-reader, after a 15-row packet in project-internal language collected 0 of 15.
- 1.0.0 (2026-08-28) — First release: the packet, the five properties, and the two
permanent fixtures.
Related
references/worked-example.md — a complete spec, the filed copies, the paste that comes
back, and the rulings file it becomes.
synthesis-reader-briefing — the four questions every packet is authored
against; the reader contract above is that skill applied to this medium.
synthesis-thinking-framework — for deciding what to recommend before you build the packet.
synthesis-anti-shortcuts — a packet whose rows hedge instead of recommending is the
asking-as-shortcut costume in a new medium.
1---2name: synthesis-decision-packet3description: Collect many parallel decisions from a principal in one sitting instead of one per turn. Generates a self-contained HTML packet — one row per decision carrying the item, the agent's recommendation, the reasoning, and a link — with buttons labeled by what pressing them does, the consequence under each button, a per-row note box, local persistence, and a paste-able summary the principal returns in a single message; files the spec, the page, and the returned rulings in the owning project's resources/artifacts/ so every agent on the project can read them. Use when you owe five or more decisions of the same shape; when a review, migration, upgrade, triage, or backlog pass has produced a list someone must rule on; or when a per-item conversation is burning round-trips.4license: Apache-2.05---67# Decision Packet89**Version 1.5.0** (2026-09-20)1011An agent that has analysed N items needs N decisions from its principal. Every default shape12fails at scale, and the measurement that produced this skill is blunt: **26 rounds of per-item13conversation produced 0 of 30 decisions. One packet produced 30 of 30, in one pass, in one14paste.**1516| Shape | Why it fails |17|---|---|18| One question per turn | N round-trips. This is what cost 26 rounds. |19| One long prose report | The principal holds thirty judgments in their head, keeps their place, and composes a reply that re-identifies each item. |20| A table in chat | Readable, not operable. Nowhere to record a decision, no state if they stop halfway. |21| A form that submits somewhere | Needs a backend, and the reply still has to get back to the agent. |2223The failure is not that the principal lacks information. **The medium collects no structure**, so24the burden of structuring the response falls on the person, every time, for every item.2526**The property to preserve above all others: the principal's cost scales with the number of27*sittings*, not the number of *items*.**2829## When to use it3031Load when you owe your principal **five or more parallel decisions of the same shape**, each32needing supporting context, where you have a defensible recommendation per item and the decisions33matter enough to deserve attention but are too numerous for per-item conversation.3435Natural fits: review findings to fix or waive · dependency bumps to take or hold · drafts to36publish, edit, or kill · files to migrate or leave · flaky tests to quarantine or fix · features37to build now, later, or never · candidates to advance on a defined rubric.3839## When NOT to use it4041- **Fewer than about five decisions.** Just ask in chat. The generator refuses below five without42 `--allow-small`.43- **The decisions are not parallel in shape.** A packet of unlike questions is a form, and a form44 is worse than a conversation.45- **You have no recommendation per item.** Then the packet is a questionnaire and *your analysis46 is not finished*. Do the analysis. The generator refuses a packet where no row carries a47 recommendation.4849The failure mode of a good pattern is over-application. These three limits are the skill.5051## The six load-bearing properties5253Requirements, not suggestions. Each is why it worked.54551. **Self-contained rows.** Item, recommendation, reasoning, and a link to the underlying56 artifact. The principal never leaves the packet to decide.572. **The recommendation is marked on the control, not merely stated in prose.** Agreeing costs one58 click. The agent's judgment does work instead of being described.59 **It is deliberately not pre-*selected*** — a packet that opens fully decided cannot distinguish60 "I agreed" from "I never looked", and would report decisions nobody made.613. **A free-text box on every row, beside the buttons.** Never force a principal into your option62 set. In the origin run one such note, on one row, carried information no button could have.634. **Local persistence keyed per item.** Thirty decisions is more than one sitting for anyone doing64 it properly. Storage access is guarded: where a browser blocks it the packet still works and65 says so in the summary.665. **A paste-able summary the tool generates.** *This is the property that closes the loop.* The67 structure you need is produced by the packet, not composed by the person.686. **Filed in the owning project, never only as a chat artifact.** The spec, the generated page,69 and the rulings the principal pastes back live in the owning synthesis project's70 `resources/artifacts/` as `<date>-<slug>-spec.json`, `<date>-<slug>.html`, and71 `<date>-<slug>-rulings.json`. A packet that exists only as a published Claude artifact is72 readable by one client in one conversation; the project directory is readable by every agent73 working the project, ChatGPT Codex included, and by the next session after this one is gone.74 `build_packet.py --file-into` files the first two; `record_rulings.py` files the third.7576## Content requirements, which matter as much as the mechanics7778- **Name filters from the content, not from generic severity.** "Needs a fix", "We disagreed",79 "Ready as written", "Not yet decided" — so the principal picks their own path through the set80 rather than going 1 to N.81- **Surface disagreement; never converge before the principal sees it.** Eight rows in the origin82 run showed two reviewers' conflicting verdicts and the principal broke all eight ties.83 Converging first would have shown a false consensus. Use the `disagreement` block.84- **A summary band before the detail**, so the shape of the work reads in three seconds.85- **Severity in form as well as words** — the coloured rail per row is how the eye finds exceptions86 while scrolling.87- **Recommend against your own prior work where that is true**, including failed hypotheses. A88 packet that only argues one way is a sales document, and the buttons stop being trusted.8990## The reader contract (v1.1.0) — comprehension is a load-bearing property9192The origin measurement has a dark twin, measured on the same principal on932026-08-29: **a 15-row packet written in project-internal language collected940 of 15 decisions.** Every mechanical property above worked — filters,95persistence, marked recommendations — and none of it mattered, because the96rows named things only the authoring session knew ("C1", "holdout",97"quarantine", "protected strata"). The principal's verdict: "written in some98alien or machine language." Structure without comprehension collects99nothing. The packets that ran 30/30 were about things the principal already100knew — articles, titles, links.101102So a packet is a **stranger-read document**, and authoring one starts where103`synthesis-reader-briefing` starts: who reads this, what do they bring, what104does it ask, what do they leave with. Then, per row:105106- **The label is plain language.** Internal IDs may appear as chips; they107 are never the name.108- **Context says what the thing IS** in words the reader already has,109 before any result about it.110- **An `impact` block states consequences, both ways** — what actually111 happens if they take the recommendation and if they don't, in outcomes112 the principal cares about (what ships, what it costs, what dies), never113 in internal treatment vocabulary. The generator renders it distinctly.114- **Options are labeled by what pressing them does,** never by a bare115 acknowledgement. Measured on 2026-09-14: on a 9-row packet, three rows116 offering "Yes, do that" / "No" collected notes instead of decisions — the117 principal could not tell what each button would do to the thing in118 question, so the note boxes carried what the buttons should have. "Keep119 them on my phone" / "Take them off my phone" is the accepted form. The generator raises a120 READER finding for a bare label (yes, no, ok, okay, cancel, accept,121 decline, approve, reject, do it, go, stop, and their "Yes, do that" /122 "No thanks" forms, compared after trimming punctuation and case, and any123 of these padded with a stopword: "Accept it", "Approve this", "Do that")124 and for two labels in one option set that do not differ in a content word;125 `--strict-reader` refuses to build, naming the row, the labels, and the126 accepted form.127- **The consequence sits on the button.** Each option button carries, under128 its label, what pressing it does: the option's own `consequence` when the129 spec gives one, otherwise `impact.accept` under the recommended option and130 `impact.decline` under every other one. The row's impact block stays as131 the row's summary; the text on the button is what the principal reads at132 the moment of choosing.133- **Every surviving term of art gets a one-clause gloss** — inline on first134 use, or in the packet-level `glossary` band.135- **`audience` names the reader.** One sentence. If you cannot write it,136 you do not know who the packet is for, and neither will they.137138`--strict-reader` makes the generator refuse a packet missing `audience` or139per-row `impact`, or carrying option labels that name no consequence. **Use it140for every packet handed to a principal.** The warnings print either way;141strictness is the difference between a warning you read and a packet they142cannot.143144## Use145146```bash147python3 scripts/build_packet.py --schema # the spec format148python3 scripts/build_packet.py spec.json -o packet.html --strict-reader149python3 scripts/build_packet.py spec.json --stdout # to a pipe150python3 scripts/build_packet.py spec.json --strict-reader \151 --file-into PROJECT/resources/artifacts/ # + <date>-<slug>-spec.json, <date>-<slug>.html152python3 scripts/record_rulings.py paste.txt \153 --file-into PROJECT/resources/artifacts/ # -> <date>-<slug>-rulings.json154```155156Write a JSON spec, generate, file, hand over the file. It is self-contained: no build step, no157dependencies, no server. It opens from disk, over a local HTTP server, or published as an158artifact, in light or dark, on a phone or a laptop.159160**File it, then hand it over.** `--file-into DIR` writes a dated copy of the spec and of the page161into DIR after a successful build. DIR is the owning project's `resources/artifacts/` and must162already exist: the generator refuses a missing directory rather than creating one where you did163not mean. `--date YYYY-MM-DD` sets the date in the names (default: today). Publish the page as164an artifact too if that is how the principal will open it; the filed copy is the one other165agents read.166167**Record what came back.** When the principal pastes the summary, save the paste to a file and168run `record_rulings.py paste.txt --file-into DIR`. It parses the exact text the packet's "Copy169summary" button emits and writes `<date>-<slug>-rulings.json` beside the spec and page: `packet`,170`ruled_on`, `decided`, `total`, and one ruling per row with `id`, `label`, `choice_label`,171`took_recommendation` (true, false for an override, null when undecided or when the row carried172no recommendation), `accepted_in_bulk` (the packet keeps bulk acceptance distinct from a173considered click, and so does the file), `recommended_label`, and `note`. It refuses text that174is not in that format, naming the line that failed, the form expected there and the text it175received; it reads a whitespace-only line as blank, since chat surfaces pad empty lines; it176refuses a paste whose row count or decided count disagrees with its own `Decided n of m.` line;177and it keeps an existing rulings file for the same date and packet unless `--replace` is passed.178Commit all three files with the project.179180## Enforcement (v1.5.0) — generator output is verified, not trusted181182Prose above tells the agent to generate; this section is what happens when183one freelances instead. Every page `build_packet.py` emits carries a184provenance marker pinning its embedded spec (`synthesis-decision-packet185spec-sha256`). `record_rulings.py` refuses a paste whose packet has no186filed `-spec.json`. And the context doctor's `skill-outputs` check fails187any packet page under a project's `resources/artifacts/` that is not188verifiable generator output: unmarked with no filed rulings is a defect189(rebuild with the generator or remove it); a marker that disagrees with190the embedded spec is a defect (never hand-edit generator output); a191closed record (unmarked but ruled) warns. The rule ships on every192machine with install, upgrade, and doctor — it is not a local note.193194**Generate from a data array; never hand-author rows.** Thirty hand-written blocks drift. One195array with a render loop cannot. That is the whole reason this is a generator rather than a196template.197198The generator validates before it emits and refuses to build a broken packet: duplicate ids199(they key persistence), ids with leading, trailing or doubled whitespace or a line break (the200summary line is `id label`, split on its first double space), a line break in the title, a row201label or an option label (the paste is one line per field, and a packet that builds must file),202an option set with fewer than two options (a one-button set records no decision), two options203in one set sharing a value or a value that is not a non-empty string (the value keys the pressed204state and the summary label through a DOM dataset, which stores strings), a recommendation205outside its own option set, a packet with no recommendations at all, malformed option sets at206the packet level or on a row, malformed disagreement, impact, or glossary blocks.207208## Two defects that are permanent fixtures209210Both shipped in the reference implementation; one reached the principal in real use. They are211regression-tested in `scripts/test_build_packet.py`.212213- **Charset in the first bytes.** Without `<meta charset="utf-8">` ahead of everything, typographic214 punctuation renders as mojibake when served over a plain local HTTP server. *Found by loading215 the page, not by reading the source* — which is why the fixture asserts on bytes and why you216 should always open a generated packet before handing it over.217- **The copy control must never fail silently.** `navigator.clipboard.writeText` is blocked inside218 a sandboxed artifact iframe with no `clipboard-write` permission; in the origin run the button219 did nothing and *said* nothing, and the principal worked around it by hand. The required order:220 **select the textarea first** so a manual ⌘C/Ctrl+C always works, say something *synchronously*,221 then `document.execCommand("copy")` (which does work in sandboxed iframes), then the async API,222 and report honestly which path succeeded. A third fixture was added on 2026-08-28 after driving223 the real button surfaced an empty status line while the async promise was unsettled — an224 unbounded silent interval is the same defect as a permanent one.225226## Relationship to other skills227228- **`synthesis-autopilot`** should *call* this, not reimplement it. Autopilot requires "batched229 questions for the user"; this is the concrete artifact that requirement was missing. A230 round-trip budget only means something if one round-trip can carry many decisions.231- **The adversarial review family** — this is where an engagement surfaces its unresolved232 disagreements. Pair it with a status for findings that are not open, not conceded, and not the233 agents' to close.234- **The handoff queue** (`synthesis-project-management/scripts/handoff.py`) moves work *between235 agents*. The decision packet moves decisions *between agent and principal*. Together they are236 the two directions that stop routing everything through a person as the transport layer.237238## Changelog239240- **1.5.0 (2026-09-20)** — Enforcement: the generator emits a provenance marker pinning the embedded spec; `record_rulings.py` refuses pastes with no filed `-spec.json`; the context doctor's `skill-outputs` check fails unmarked live packets as defects. After a session hand-authored two packets.241- **1.4.0 (2026-09-14)** — Option labels must name consequences: READER findings for bare242 acknowledgements and for two labels that do not differ in a content word, fatal under243 `--strict-reader`, with the row, the labels, and the accepted form in the message. Each option244 button carries its consequence under the label (`consequence` per option, else the row's245 `impact` mapped onto the buttons). Sixth load-bearing property: packets and rulings are filed246 in the owning project's `resources/artifacts/` — `--file-into`, `--date`, and the new247 `record_rulings.py`, whose parser is pinned to the summary format the page emits. Per-row248 option sets are validated like the packet-level set; ids with edge or doubled whitespace are249 refused. The worked example's options now name outcomes. Repair round the same day: a line250 break in the title, a row label, an option label or an id is refused, so a packet that builds251 can always be filed; a one-button option set, two options sharing a value, and a non-string252 value are refused; an acknowledgement padded with a stopword ("Accept it", "Do that") is253 caught; `record_rulings.py` counts the title underline in UTF-16 units as the page does,254 reads whitespace-only lines as blank, and every refusal quotes the text it received; the255 worked example's paste and rulings file match its two-row spec, and a fixture holds them to256 the parser.257- **1.3.0 (2026-08-29)** — Ids must be non-empty strings: JSON `1` and `"1"` become the same258 localStorage key, so JSON-distinct ids could share saved state.259- **1.2.0 (2026-08-29)** — The relationship section names the handoff queue as the260 agent-to-agent transport.261- **1.1.0 (2026-08-29)** — The reader contract: `audience`, `glossary`, per-row `impact`, and262 `--strict-reader`, after a 15-row packet in project-internal language collected 0 of 15.263- **1.0.0 (2026-08-28)** — First release: the packet, the five properties, and the two264 permanent fixtures.265266## Related267268- `references/worked-example.md` — a complete spec, the filed copies, the paste that comes269 back, and the rulings file it becomes.270- `synthesis-reader-briefing` — the four questions every packet is authored271 against; the reader contract above is that skill applied to this medium.272- `synthesis-thinking-framework` — for deciding *what* to recommend before you build the packet.273- `synthesis-anti-shortcuts` — a packet whose rows hedge instead of recommending is the274 asking-as-shortcut costume in a new medium.