exploring-an-idea
Exploring an Idea
Section titled “Exploring an Idea”A design document reads as settled fact whether or not anyone checked. That is the whole failure mode: an unverified sentence in a PCD becomes an interface, then a dependency, then a week of work, and nothing in the process ever asks “how do we know that?”
This skill exists to put that question before the document instead of after it.
The case this was written from
Section titled “The case this was written from”Needledrop (NFC tap-to-stream for record collectors) had an unusually good PRD and PCD — real non-goals, numeric kill criteria, a named Phase 0 with exit criteria. It still shipped four false premises into its architecture, and the Phase 0 spike had to correct all four:
| The PCD asserted | Measurement found |
|---|---|
| “Spotify is effectively closed to us” (5-user dev cap, 250k-MAU quota wall) | Those gate user-data endpoints. Client credentials reach /v1/search and /v1/albums today. Spotify became the best-covered platform. |
| Barcode/UPC is the strongest identifier — “UPC preferred” | Discogs carries the physical pressing’s barcode; streaming keys the digital release UPC. Different numbers. Spotify UPC lookups hit 0/46, Apple 5/42. |
| Odesli is the hedge for the Spotify gap | Odesli returned 0 Spotify links in 19 Apple-seeded album responses. It could not be the hedge. |
| Apple Music needs a MusicKit credential | Keyless for UPC and tracklist lookup. |
None of these were sloppy. Each is a plausible reading of vendor documentation. They were wrong anyway, and the cost was concrete:
- An entire provider-abstraction layer —
LinkResolver, capabilitiesOptionSet, chain-of-resolvers — designed around swapping away from a dependency that turned out to be unnecessary. - A “blocking, business” open question (email Odesli for commercial terms, owner assigned, week 1) generated by a gap that did not exist.
- A long “Option D — build our own resolver” analysis premised on Spotify being unreachable.
- The real finding — tracklist matching, which took the album shelf from 62.1% to 79.3% and Spotify from 0% to 60% — appears nowhere in the PCD, because the document had already decided the identifier ladder.
And the name: the PRD listed “run the trademark pass” as an open question,
after the docs were titled Needledrop and the repo was created as
needledrop. The check ran later, “Needledrop is not viable” came back, and
the project renamed to Sleevetap — while the repo, its remote, and its clone
path still say needledrop today.
The lesson is not “write better documents.” It is that a design doc is the wrong instrument for discovering whether something is true.
The method
Section titled “The method”1. Split the idea into claims, and label every one
Section titled “1. Split the idea into claims, and label every one”Before writing anything designed to be read as a plan, list the claims the idea rests on. Give each a provenance label — no exceptions, including the obvious ones:
measured— you ran it and saw the result. Cite the number.documented— a vendor doc says so. This is not verification. Every false premise above wasdocumented.assumed— someone’s reasonable belief.
Then ask of each: if this is wrong, does the design change? A claim that is
documented or assumed and load-bearing is not a claim. It is a task.
For Needledrop that filter alone catches all four: “Spotify is closed” was documented and load-bearing; “UPC is the strongest identifier” was assumed and load-bearing.
2. Probe the load-bearing ones — cheaply, before the document
Section titled “2. Probe the load-bearing ones — cheaply, before the document”A probe is a script that answers one question with a number, not a prototype. Hours, not days; throwaway, not the first commit of the product.
The Needledrop probes would have been: one curl with Spotify client
credentials (kills premise 1 in ten minutes); twenty barcodes from a real
collection run against three catalogues (kills premise 2 in an hour).
In Claude Code, write and run them. In Claude Desktop, you cannot — so name each probe, say what number would settle it, and hand them over as the first work item. A doc that ships with “unprobed” next to a load-bearing claim is honest; one that reads as settled is not.
Probe on real data, not a sample you constructed. Needledrop’s collection turned out to be 28% 7-inch singles, which resolve at 0% because a single has no album-level streaming entity. A flat rate across that collection answers the wrong question — and only real data reveals it.
3. Write the document around what survived
Section titled “3. Write the document around what survived”Now write the PRD/PCD (see core “Docs Taxonomy” for where they live). Rules that keep it honest:
- Every technical claim carries its label.
measuredclaims cite the number. - Do not design for a risk you have not confirmed. The provider abstraction is the cautionary tale: a real pattern, applied to an imaginary problem, and the cost was not the code but the architecture built around it.
- Kill criteria are numbers with a decision attached: “auto-match below 70% on a real collection → revisit” is a kill criterion. “Validate feasibility” is not.
- When a probe contradicts the document, amend the document in place and say what was wrong. Needledrop did this well: the PCD was corrected rather than left asserting things known to be false. A doc nobody trusts is worse than no doc.
4. Do the irreversible-cheap things first
Section titled “4. Do the irreversible-cheap things first”Some decisions cost nothing now and a rename later. Do them before the doc is titled:
- Name: saturation + trademark pass, and check the term isn’t already loaded in the domain (“needle drop” is a music-licensing term of art).
- Repo name: follows the name check, never precedes it.
- Domain / handles: check availability while the name is still free.
- Stack: name it in the doc, as an archetype
(
lacquer init --list-stacks), then pass it tolacquer init --stack <name>.
Needledrop got the order backwards and still carries the wrong repo name.
The stack one is subtler than it looks. Needledrop’s PCD said “iOS app with a
web backend” and was right — but the repo was bootstrapped during the
TypeScript-only Phase 0 spike, so detection recorded profiles = ["web"],
correctly, and nothing ever looked again. Swift landed the next day with no
hooks, no CI, and no CLAUDE region; 191 tests were run by nothing at any gate
for a month. Declaring the whole stack up front is a one-word difference at
init and it gates the halves that don’t exist yet.
Two claims about the same project were held to completely different standards: the API premises got a Phase 0 spike, and “an iOS app with a web backend” got written down and never checked against what the repo was actually enforcing. A claim about your own build is still a claim.
What good looks like
Section titled “What good looks like”You are ready to leave exploration when:
- Every load-bearing claim is
measured, or explicitly flagged as unprobed with the probe named. - The kill criteria are numbers, and you would actually stop.
- One end-to-end path has been proven on real data — not a diagram of one.
- The name has survived a trademark pass.
- The stack is named as an archetype and passed to
lacquer init --stack, so every half of it is gated from the first commit.
Anti-patterns
Section titled “Anti-patterns”- The confident PCD. Fluent architecture built on
documentedpremises. Fluency is not evidence. - Abstraction as insurance. A pluggable layer against a dependency risk nobody measured. Measure the risk; then decide if the seam is worth it.
- Phase 0 as ceremony. A phase whose exit criteria are activities (“outreach”, “investigate”) rather than numbers. Needledrop’s Phase 0 was real precisely because criterion #2 demanded a measured percentage.
- Naming last. The one decision that gets more expensive every commit.
- Onboarding the spike. Running
lacquer initinside a Phase 0 throwaway records the spike’s stack as the project’s stack. Detection is right about what is on disk and wrong about what the project is;--stackis how you say which one you meant.