---
name: design-thinking
description: Turn an engineering contract or local implementation request into a concrete implementation graph covering domain shapes, success flow, execution semantics, expected failures, contextual capabilities, trust boundaries, runtime policies, resource lifetimes, targeted verification, and code. Use when deciding how a known system contract becomes implementation. Do not own product behavior, system architecture, code review, security review, production operations, or non-trivial test strategy. Use Effect-specific A/E/R, Layer, Stream, Scope, and API conventions only when the repository actually uses Effect or the task explicitly requests Effect-style modeling; inspect the installed Effect version before generating version-specific code.
---
# Design Thinking
Design Thinking turns a known engineering contract into an implementation graph.
```text
X → Implementation Graph → Program
│ │ │ │ │
│ │ │ │ └─ contextual capabilities (§5)
│ │ │ └──── expected failures (§4)
│ │ └─────── successful values/flow (§2)
│ │
│ └─ Effect when Effect is used
│
└─ engineering contract or local implementation task
```
```text
§1 Shapes domain records, IDs, variants, errors
§2 A successful transformation / call graph
§3 Execution cardinality, ordering, concurrency, interruption
§4 E / Cause expected failures, defects, interruption, recovery ownership
§5 R contextual capabilities and provisioning
§6 Boundaries decode external ingress; encode external egress
§7 Policies retry, timeout, caching, tracing, limits, recovery
§8 Scope resource and child-work lifetime
§9 Verification smallest faithful proof
§10 Effect project graph onto the installed Effect version when applicable
§11 Code implementation paths should trace back to the graph
```
Read the engineering contract or local task. Model only the implementation semantics that matter. Then write code whose consequential paths can be explained by that graph.
Do not redesign the product or system architecture here.
## 1. Name the shapes
Define the values that actually move through the implementation.
Typical shapes:
- **Records** — domain values with meaningful fields.
- **IDs** — identifiers when identity matters.
- **Variants** — explicit alternatives or states.
- **Errors** — expected failure values when callers can meaningfully handle them.
- **Commands / events** — when behavior is driven by requests or emitted facts.
Use stronger types when they remove real ambiguity or invalid states.
Do not require every ID to be branded, every primitive to become a wrapper, or every internal structure to become a named domain type.
Prefer the simplest representation that preserves the important invariant.
Example:
```text
BookingRequest
Booking
RoomId
BookingStatus = Pending | Confirmed | Cancelled
BookingConflict
```
The shapes are the nouns. The graph is the behavior between them.
## 2. Think A first: successful flow
Map the successful transformation before error handling.
```text
Input
↓
F1
↓
F2
↓
F3
↓
Output
```
For each node ask:
```text
What value comes in?
What value comes out?
What state changes?
What external effect occurs?
```
The happy path should remain easy to read even after failure and runtime policy are added.
Do not force every operation into a service or abstraction. A local function can stay a local function when that is where the behavior belongs.
If the system contract already defines responsibility boundaries, preserve them unless implementation evidence shows a real contradiction that must go back to `engineering-design`.
## 3. Model execution semantics separately
Cardinality, execution ordering, concurrency, and freshness are different concerns.
### Cardinality
Ask whether the computation produces:
```text
one result
or
incremental / many results over time
```
A collection returned once is still a one-shot result:
```text
Program, ...>
```
It does not automatically require a stream.
Use streaming only when incremental consumption, continuous emission, backpressure, or long-lived flow is actually part of the behavior.
### Ordering
Mark edges where order matters:
```text
A
↓
B
↓
C
```
Do not parallelize steps merely because they can technically run concurrently.
Example:
```text
reserveStock
↓
chargePayment
```
may require ordering because the state transition semantics depend on it.
### Concurrency
When independent work may overlap, mark the intended execution explicitly:
```text
fetchProfile ──┐
├─ parallel → composeUser
fetchSettings ─┘
```
Possible execution forms include:
```text
sequential
parallel
bounded concurrency
race
background / forked
```
For concurrent work ask:
```text
What may run at the same time?
What ordering must still hold?
What shared state exists?
What happens if one branch fails?
What happens if the parent is cancelled/interrupted?
```
### Freshness / caching
Caching is a policy axis, not cardinality.
When caching matters define only the semantics required:
```text
always recompute
memoized
TTL
explicit invalidation
deduplicate concurrent lookups
```
Do not add caching because a result is "time-bounded" unless the product/system contract actually permits stale data and benefits from reuse.
## 4. Model failure correctly: E, defects, interruption
Do not treat retry, fallback, and `die` as equivalent kinds of failure.
Model outcomes as:
```text
Node
↓
Outcome
├─ Success A
│
├─ Expected failure E
│ ├─ propagate
│ ├─ map
│ ├─ recover
│ ├─ retry
│ └─ fallback
│
├─ Defect
│ └─ unexpected/programmer/runtime failure
│
└─ Interruption
└─ cancellation + finalization semantics
```
### Expected failure
Use `E` for failures that are part of the program's expected contract and can be reasoned about by callers.
Examples:
```text
BookingConflict
NotFound
PermissionDenied
RateLimited
ValidationError
```
For each expected failure decide who semantically owns the policy:
```text
operation itself
caller
service boundary
transport boundary
```
Handle it at the narrowest boundary that actually owns the recovery policy.
Example:
```text
loadCache(id)
└─ CacheMiss
↓ local recovery
loadDatabase(id)
```
This local recovery is clearer than forcing every error to an outermost handler.
### Defect
A defect is not just another `E` strategy.
Examples:
```text
impossible invariant
unexpected null from trusted code
bug in implementation
unrecoverable runtime assumption
```
Normally allow defects to propagate to the runtime/appropriate boundary unless there is a deliberate containment policy.
Do not convert every defect into a domain error merely to make the type look complete.
### Interruption
When work can be cancelled/interrupted, reason about:
```text
what may stop
what must finish
what must be cleaned up
what state may already have changed
what child work inherits cancellation
```
Interruption semantics matter especially around concurrency and resources.
### Error translation
Translate low-level failures where abstraction ownership changes.
Example:
```text
SqlError
↓ repository boundary
DatabaseError
↓ service boundary
BookingFailure
↓ transport boundary
API error
```
Do not leak implementation-specific failures through boundaries that should hide them.
## 5. Model R as contextual capability, not every dependency
`R` represents capabilities required from context to execute the computation.
Good candidates:
```text
UserRepository
Clock
EmailSender
Config
PaymentGateway
FileSystem
```
Not every value belongs in `R`.
Use ordinary arguments for request/local data:
```text
findUser(userId)
```
`userId` is data, not a contextual service.
Distinguish:
```text
request/local data
→ function arguments
stable substitutable capability
→ R / contextual service
dependency required only to construct a service
→ satisfy during construction / Layer
```
Do not leak service-construction dependencies into every consumer merely because the implementation happens to need them internally.
Use dependency injection only where substitution, lifecycle, configuration, or boundary ownership makes it useful.
Do not create one service interface per function.
## 6. Decode at every external trust transition
Every external ingress is a trust boundary.
Examples:
```text
HTTP request
message queue payload
third-party response
environment/config
file contents
database data crossing a separately trusted boundary
CLI input
webhook
```
Model:
```text
unknown / external
↓
decode / parse / validate
↓
domain representation
```
Inside that validated boundary, do not repeatedly parse the same value without a new trust transition.
But decoding proves only the constraints represented by the decoder/schema.
It does not automatically prove:
```text
authorization
ownership
freshness
business invariants
cross-record consistency
transaction safety
```
Those belong to their actual semantic owners.
At external egress, encode to the external contract when needed:
```text
domain value
↓
encode / serialize
↓
external representation
```
Reuse one schema only when the semantics are genuinely the same.
Do not force:
```text
transport representation
persistence representation
domain representation
```
to share one schema merely to avoid duplication.
When the repository uses Effect Schema, use the installed version's decode/encode APIs.
## 7. Compose runtime policies as real semantics
Runtime policies include:
```text
retry
timeout
recovery/fallback
caching
logging
tracing
rate/concurrency limits
scheduling
```
These are not decoration.
They can change:
```text
A
E
R
control flow
number of executions
timing
cancellation behavior
resource usage
```
Keep policies visually separate from the core domain flow when that improves readability, but model their semantic effect.
### Retry safety
Never derive:
```text
network timeout
→ retry
```
automatically.
First ask:
```text
Can the operation be repeated safely?
```
Model:
```text
retry candidate
↓
repeat-safe / idempotent?
├─ yes → define retry policy
└─ no → define one of:
idempotency key
deduplication
status reconciliation
uncertain-result handling
no automatic retry
```
Example:
```text
chargePayment
↓ response timeout
```
Blind retry may double-charge unless the operation has a safe repeat strategy.
### Timeouts
Timeouts are semantic choices.
Ask:
```text
What happens to the underlying work after the caller times out?
Can it still commit?
Can the caller retry?
How is an uncertain result reconciled?
```
Do not treat timeout as equivalent to "operation did not happen."
### Caching
If caching changes freshness or consistency, make that visible in the graph.
### Logging/tracing
Instrumentation should observe the implementation without becoming the implementation's domain control flow unless the contract actually depends on it.
## 8. Scope resources and child work
For resources such as:
```text
database connections
file handles
sockets
streams
child processes
temporary resources
subscriptions
```
model:
```text
acquire
↓
owner scope
↓
use
↓
child work / fibers
↓
release/finalization
```
The implementation must make resource lifetime explicit enough that cleanup follows the intended owner lifecycle.
When Effect is used, scoped resource combinators and `Scope` should represent that lifetime according to the installed version.
Do not state resource cleanup as a generic "type guarantee." The guarantee comes from using the runtime's scoped acquisition/finalization semantics correctly.
For forked/background work ask:
```text
Does it belong to the parent's scope?
Should interruption cancel it?
Can it outlive the request?
Who owns cleanup?
```
Unowned background work is a lifecycle bug waiting to happen.
## 9. Choose the smallest faithful verification
Verification is part of implementation, but not every test problem belongs here.
For obvious cases:
```text
pure business rule
→ targeted unit test
simple bug
→ direct regression test
existing integration pattern clearly proves it
→ use that pattern
```
Use `test-engineering` when testing itself requires design:
```text
transaction semantics
concurrency/race
migration compatibility
async/queue semantics
failure/recovery
complex integration boundary
flaky/brittle suite
```
### Substitute R only when fidelity is preserved
A fake dependency is useful when the property under test lives above that boundary.
Examples:
```text
business decision
→ fake repository may be sufficient
time-based retry policy
→ controllable/test clock may be sufficient
```
Use the real dependency when correctness belongs to that dependency's semantics:
```text
SQL uniqueness
→ real DB
transaction isolation
→ real DB + concurrency
filesystem semantics
→ real filesystem boundary
HTTP serialization
→ real integration boundary
migration behavior
→ real migration against representative schema/data
```
Do not claim that swapping `R` proves the whole graph correct.
It proves only the behavior whose semantics are preserved by that substitution.
Core rule:
```text
Choose the smallest test
that faithfully proves
the actual risk.
```
## 10. Project to Effect only when Effect is actually used
The graph model is language/framework-neutral.
Use Effect-specific projection only when:
```text
the repository uses Effect
or
the task explicitly requests Effect-style implementation
```
Before writing Effect API-specific code:
```text
inspect package.json / lockfile
↓
identify installed Effect version
↓
use matching repository APIs/docs/patterns
```
Do not write APIs from another Effect version.
### A / E / R projection
When applicable:
```text
A
→ success value/data flow
E
→ expected typed failure
R
→ contextual capabilities required to run
```
Defects and interruption are not simply extra variants of `E`; reason about the runtime/Cause model appropriate to the installed version.
### `Effect.gen`
Use `Effect.gen` when it makes sequential success flow clearer.
Do not impose:
```text
gen body = all A
outer pipe = all E
```
as a correctness invariant.
Better rule:
```text
Keep the happy path visually dominant.
Handle an expected failure at the narrowest boundary
that owns its recovery policy.
```
Examples:
```text
uniform recovery for the whole workflow
→ outer composition may be appropriate
operation-specific fallback
→ local handling around that operation
boundary error translation
→ handle at the boundary
caller-owned failure
→ propagate in E
```
### `.pipe()`
`.pipe()` is composition syntax, not proof that the graph is unchanged.
Combinators inside a pipe may alter:
```text
A
E
R
execution count
timing
concurrency
cancellation
resource behavior
```
Use `.pipe()` where it improves composition/readability, while reasoning about the real semantics of each combinator.
### `Layer`
Use Layer/provisioning to satisfy contextual capabilities and service-construction dependencies where appropriate.
Do not expose construction dependencies in service interfaces when they can be discharged during provisioning.
### `Stream`
Use Stream for incremental/multi-emission computations when its semantics are actually needed.
Do not convert every collection or paginated result into Stream by default.
## 11. Write code that traces to the graph
The graph is a model, not an absolute source of truth.
Every consequential implementation path should trace back to it.
Consequential means behavior such as:
```text
data transformation
state transition
dependency
expected failure
recovery policy
concurrency/ordering
trust boundary
resource lifetime
external effect
```
If code introduces consequential behavior that the graph cannot explain:
```text
either the implementation is wrong
or the graph is incomplete
```
Update the correct side.
Do not force minor syntax, helper extraction, or incidental language detail into the graph.
The graph exists to make important semantics explicit, not to mirror every line of code.
---
## Relationship to other skills
### `product-design`
Owns:
```text
what product behavior is required
```
Design Thinking does not reopen product scope during implementation.
### `engineering-design`
Owns:
```text
system responsibilities
state ownership
boundaries/contracts
system guarantees
architectural decisions
```
Design Thinking consumes that contract and decides how the local software graph implements it.
If implementation reveals an architectural contradiction, return the issue to `engineering-design`.
### `design-graph`
Use when implementation includes non-trivial interface interaction/state.
`design-graph` owns surface/move/void/attention modeling.
Design Thinking owns the software/runtime behavior behind that interface.
### `test-engineering`
Design Thinking owns obvious targeted verification.
`test-engineering` owns verification when the proof boundary or environment itself requires design.
### `security-review`
Design Thinking implements known security controls.
`security-review` determines whether changed trust/authority/data paths violate a security property.
### `code-review`
Design Thinking is forward reasoning:
```text
intent / engineering contract
→ implementation graph
→ code
```
Code Review is reverse reasoning:
```text
code / diff
→ actual graph
→ compare with intent
```
Do not use Design Thinking as a substitute for post-implementation review.
### `production-ops`
Production Ops owns runtime health, observability, incident response, and recovery requirements.
Design Thinking implements application-level changes required by those operational properties.
---
## Proportionality
Depth follows implementation risk.
```text
tiny local change
→ inspect → change → targeted verification
normal behavior change
→ shapes + success flow + expected failure + dependencies
state / boundary / concurrency / resource change
→ model the affected execution graph explicitly
complex/high-risk implementation
→ use the full method
```
Do not force all sections onto every change.
Use only the parts that can change correctness or maintainability.
---
## The Pipeline
```text
ENGINEERING CONTRACT / LOCAL TASK
↓
"What values exist?"
→ define only meaningful shapes
↓
"What successful transformation must happen?"
→ draw A: implementation/call graph
↓
"What ordering, cardinality, concurrency, or interruption matters?"
→ execution semantics
↓
"What expected failures exist?"
"What defects/interruption matter?"
→ E / runtime failure model
↓
"What contextual capabilities are required?"
→ R / provisioning
↓
"Where does external data cross trust boundaries?"
→ decode ingress / encode egress
↓
"What runtime policies change semantics?"
→ retry / timeout / cache / recovery / limits
↓
"What resources or child work have lifetimes?"
→ scope / finalization
↓
"What is the smallest faithful proof?"
→ targeted verification
→ test-engineering only if proof itself is non-trivial
↓
"Does this repo actually use Effect?"
├─ no → preserve language-native implementation
└─ yes → inspect installed version and project graph to Effect
↓
CODE
```
**Every consequential implementation path should trace to the graph. If code introduces behavior, dependency, failure, concurrency, boundary, or lifetime semantics that the graph cannot explain, either the implementation or the graph is incomplete.**