| name | effect |
| description | Use effect-aware thinking whenever designing, writing, changing, reviewing, or debugging code. Make computational effects, dependencies, failures, state changes, control flow, resource lifetimes, concurrency, cancellation, and execution boundaries explicit, including algebraic operations and handlers when present. Follow the project's existing tools, use the matching guide for Algebraic Effects, Effect, Cats Effect 3, or ZIO, learn the exact public API when a system is involved, keep meaning intact from design through execution, and verify the real behavior. When an effect system owns the work, keep every supported capability inside it and require the user's explicit approval before any unavoidable escape hatch. |
Effect
Make effects explicit
A computational effect is behavior that a returned plain value alone does not fully describe. It may read or write data, use time or random values, change state, fail, branch, suspend, transfer control, resume work, use a resource, or contact another system.
In an algebraic effect system, a computation may request a named operation whose meaning is supplied by a handler. Such an operation may express control, state, failure, or another behavior without contacting anything outside the program. Do not assume that an effect library, an effect type, and an algebraic effect handler are the same mechanism. Learn what the project means by each term.
Make each important effect visible in the form that the project can support. This may be a type, value, return result, exception, function signature, interface, dependency, runtime contract, or clear control-flow boundary.
Do not assume that every effect needs a special library or type. Do not hide important work only because the current language cannot state it in a static type.
Start from the meaning of the work. Ask:
- What behavior cannot be understood from the returned value alone?
- Which inputs, services, state, and outside systems does it need?
- What can succeed, return no value, refuse, fail, or stop early?
- Which state can change, and who owns that change?
- Which resources must be opened, used, and closed?
- Can work overlap, time out, or be cancelled?
- Where does description or preparation become execution?
- Which source and checks prove the model and the code?
Keep plain calculations plain. Keep effects clear where they enter, compose, and run.
Keep work inside the effect system
When an explicit effect system owns a part of the program, use it for every behavior and integration that it can express. Build in the system from the start instead of first controlling the work with host-language effects and wrapping it later. A pure value or calculation may remain plain only when it owns no effect, resource, lifetime, or running work.
An escape hatch is a direct host-language, foreign-library, runtime, or unsafe operation that bypasses guarantees owned by the effect system and must be adapted back into it. In an algebraic effect system, supported operation declarations, requests, forwarding, handlers, legal resumptions, interpreters, and owned execution boundaries are not escape hatches merely because they give effects their meaning. Treat a real bypass as a last resort, not a convenience.
Before adding, expanding, or relying on an escape hatch:
- Search the exact installed system's public API, source, tests, and project patterns for an effect-native construction.
- Show why every relevant effect-native path cannot meet the real requirement.
- State the smallest proposed boundary, its contract, and its risks for errors, defects, cancellation, resources, concurrency, and observability.
- Ask the user for explicit approval of that specific boundary and reason.
Do not treat silence, a broad request to finish the task, an existing dependency, or approval of another boundary as consent. Without explicit approval, do not implement the escape hatch; report the blocked choice and keep the effect-native work complete.
If the user approves it, isolate the smallest adapter, expose its full contract, restore the effect-system model immediately, and verify every outcome. Never let raw control flow, errors, resources, tasks, or unsafe values leak beyond that boundary.
Learn the effect model in front of you
Treat the project and its matching sources as the truth. Do not force a remembered pattern onto a different language, library, or codebase.
- Read the project settings, build files, manifests, lockfiles, and language or runtime versions for the part being changed.
- Read existing code to learn how the project represents input and output, state, failure, resources, delayed work, concurrency, and cancellation.
- Find the language features, standard-library tools, framework rules, or effect libraries that own those concerns.
- When a library is involved, resolve the exact installed version and public surface with the project's own build or package tools.
- Read public documentation, signatures, declarations or headers, implementation, behavior tests, and type tests as available.
- Match fuller source to the installed release before trusting it. Do not silently use a moving branch or a different version.
Use each source for its proper job: public documentation and signatures define supported use, implementation explains runtime meaning, and tests give behavior and boundary evidence. Never import a private path or copy an internal helper merely because reading it was useful.
If the project has no effect library, use its normal language and framework tools. Do not add a library only to make the code follow a preferred style. Add a new tool only when the real need justifies its cost and the user has authorized that change.
If exact source cannot be reached, say what evidence is available and what remains uncertain. Do not hide the gap with a fixed recipe.
Load the matching system guide
Read the guide for the system that owns the work before choosing APIs:
- For language-native algebraic effect operations and handlers, a system that calls algebraic effects abilities, or an explicit free, freer, or extensible-effect encoding, read Algebraic Effects completely.
- For the TypeScript
effect package or @effect/* modules, read Effect completely.
- For Scala Cats Effect 3 and its official modules, read Cats Effect completely.
- For Scala ZIO and its modules, read ZIO completely.
The TypeScript package named effect, Cats Effect, and ZIO are not algebraic effect handler systems merely because their names or types contain the word effect. Load only the guide that matches the mechanism in the current part. If different parts use more than one system, apply each guide only at the boundary it owns. For Cats Effect 2 or another effect system, follow this main workflow and inspect that system's exact public sources instead of treating another guide as a fixed recipe.
Carry the model through the whole workflow
Use one effect model from the first question to the final proof:
- Study the real need and current program.
- State the computational behavior, dependencies, results, failures, state changes, control flow, lifetimes, and execution boundary. Include operations and handlers when the mechanism provides them.
- Choose the existing project forms that express those facts.
- Build the operation so each step keeps the facts visible that later code must handle.
- Join it to the rest of the program without changing its meaning at a boundary.
- Execute at an owned edge where dependencies, cleanup, failure handling, and cancellation are ready.
- Verify static guarantees where they exist, then test the runtime behavior and final result.
- Teach the small part of the model that makes the solution reusable.
Do not build important effects in hidden control flow and label them only after the fact. Use an effect-native adapter when one exists. If non-effectful host, framework, or third-party contact is unavoidable, apply the escape-hatch approval rule, keep the approved contact narrow, and bring its result, failure, and lifetime back into the program's visible model at once.
Keep the important facts visible
For every important operation, show the facts that callers need:
- Requirements: input, services, state, authority, and settings needed to run.
- Results: every useful success value and the meaning of no value.
- Failures: expected refusal or error, unexpected defects, and which code may handle each one.
- State: what may change, who owns it, and what other work may observe.
- Resources: how acquisition, use, cleanup, and partial failure fit together.
- Time and concurrency: delay, timeout, retry, cancellation, ordering, and ownership of work.
- Algebraic control, when present: requested operations, handler scope and order, forwarding, residual effects, and whether suspended work may stop or resume once or more than once.
- Execution: the point where prepared work actually starts and where its outcome is observed.
Use the strongest clear form the project already supports:
- When the type system can carry requirements, results, or failures, keep those links in the types.
- When the language uses exceptions, callbacks, asynchronous control flow, context, or protocols, make their contract and ownership clear at the interface.
- When checks happen at runtime, validate at the boundary and make each possible outcome visible to later code.
- When no explicit effect system owns the part and direct imperative code is the clearest fit, isolate the effect and keep its inputs, outputs, failure, and cleanup explicit. Do not wrap it in machinery that adds no useful guarantee.
No one style is always right. Judge the form by whether it keeps the needed meaning visible and makes correct composition, execution, testing, and repair easier.
Follow the need through the source
Search by meaning before searching by a remembered function name.
- Start from the project's public surface and the source tree of the feature or dependency that owns the need.
- Read the public contract first. Understand what callers may rely on.
- Follow the definition into implementation only far enough to understand runtime behavior and important conditions.
- Read the nearest behavior and type checks to learn how the contract composes and where its proof ends.
- Follow related public definitions until the chosen construction fits the whole program, not only the local line.
- Read change history when installed code, project code, documentation, and remembered use disagree.
Before inventing a helper, wrapper, local protocol, or dependency, search the project and relevant public source for an existing expression of the same idea. Prefer an existing form when it preserves the needed meaning. Repair a stale pattern when current project evidence shows a clearer or safer form.
Do not stop at the first construction that builds or runs. Confirm that its meaning, checking behavior, runtime behavior, and place in the complete program all fit the need.
Keep execution at an owned boundary
Prepare and compose work before running it when the project's model supports that split. Otherwise keep the start of observable work clear in the control flow.
At the execution boundary:
- provide every required dependency and permission;
- decide who handles each expected failure and unexpected defect;
- own resource cleanup on success, failure, timeout, and cancellation;
- make concurrent work, ordering, and shutdown rules clear;
- expose only the result and effects that the caller is meant to observe.
Do not start work in a helper, constructor, import, callback setup, or background task when its owner cannot see, stop, await, or clean it up. If the host or framework controls execution, state that boundary and follow its lifetime rules.
Prove and teach the result
Use the project itself as the final check:
- Make the smallest disposable probe when a public contract or composition is still unclear.
- Run the project's compiler, type checker, static analyzer, or contract checks where they can prove part of the model.
- Test success, refusal, failure, cleanup, cancellation, ordering, and no unwanted effect where each guards a real unproved risk.
- Check the complete path when the work crosses a process, storage, framework, or outside-system boundary.
- Confirm that library use is public and matches the installed version.
- Remove temporary probes and keep only evidence that belongs in the project.
Lead the handoff with the result. Then explain, in a few useful sentences:
- the effect model used for the work;
- the project or library sources that established it;
- why the chosen form keeps requirements, outcomes, and execution clear;
- how to follow the same source path for the next nearby question.
Keep the teaching proportional. Give the user a reusable way to think and discover without turning a small task into a long lesson.
Finish only when the important effects and execution boundaries are visible, the chosen form fits the existing project, static claims match their proof, runtime behavior has fitting checks, and every resource or running task has a clear owner.