designing-interfaces
Skill1 starsChanged 30 days agoMITView on GitHub
Use when writing or changing code other code will call — a new type, function, method, module, or package — before the implementation exists. Also when a caller needs data or behavior a module does not currently expose, when widening an existing interface, adding a getter, or deciding where new logic should live.
2 other projects use a version of this.
Adopted by
Most recently taken up by
- JustYannicc/skillstheir own version, first seen Nov 1, 2026
- wdm0006/python-skillstheir own version, first seen Oct 25, 2026
Other versions2 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 2
- wdm0006/python-skills★ 111same name
- JustYannicc/skills★ 3same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository11 other files it gives its agents.
- addressing-findingsSkill
- analyze-codeSkill
- brainstormSkill
- coding-disciplineSkill
- guiding-manual-testingSkill
- knowledge-baseSkill
- researchSkill
- style-checkerSkill
- test-driven-developmentSkill
- using-software-specialistsSkill
- writing-unit-testsSkill
The file
We split this skill into the rules it gives an agent
3 rules. About 0 are common, 3 are distinctive, and 0 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈3
Distinctive · estimate
This author's own choices. Worth a look.
0
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 35
It is as small as possible: Every added function or method is a permanent cost to every caller.
- Distinctive: line 79
Deletion test: Delete the module. If complexity vanishes, it was a pass-through and should not exist.
- Distinctive: line 101
When the two conflict, minimality applies to the implementation, never to the caller's required knowledge.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Designing Interfaces
A zero-line diff is not a zero-cost change. The cheapest change to write is usually the one that moves complexity out to the callers, where no diff records it.
An interface here is everything a caller must know to use a module correctly: the signature, plus the types it drags along, the constants, the ordering rules, the error modes, and the invariants. Not just the type-level surface.
The Contract
REQUIRED: write this before the implementation. Put it in your reply, not in a comment. Write it for a reader who has not seen this session: plain sentences, real code, no shorthand.
WHAT: <what it does, in plain sentences>
WHERE: <the module it lives in; why there, when that isn't obvious>
INTERFACE: <each signature, as code; under it, each input and output in a plain
sentence: what it means, including empty/nil/zero, and when it errors>
USE: <the caller's code using it. When an existing call site changes, show
it before and after>Then check it against these principles. A design that breaks one is redesigned before any code is written.
- It does real work for the caller. Broken when WHAT only says it returns or exposes something the module holds.
- It keeps complexity inside. Broken when USE shows the caller filtering, rendering, ordering, or interpreting what the module could do itself.
- It follows the single responsibility principle. Broken when WHAT joins unrelated jobs with "and". Steps of one job ("locks, renders, writes") are one job; the test is whether a caller could want one part without the other.
- It takes only the inputs it strictly needs. Each input is something only the caller can decide. Broken by an input the module already has or could default, or by a whole object passed when one field is used.
- The function and its parameters are descriptive, and follow the language's naming conventions (Go:
ActiveUsers, notGetActiveUsers; Python:retry_after). If only vague names fit (Manager,Helper,Data,Process), the design is unclear: fix the design, not the name. - It avoids error cases it could handle itself. Broken when INTERFACE lists an error the function could define away, such as "not found" where an empty result would do.
- It is hard to use wrongly. Broken by adjacent parameters of the same type that are easy to swap, or by a rule the caller must remember that the types could enforce.
- It hides its internal representation. Broken when INTERFACE or USE exposes how the module stores its data.
- It is as small as possible. Every added function or method is a permanent cost to every caller; see below.
- It receives its dependencies, not creates them. I/O, clock, network, and randomness are passed in, so tests can replace them; see Seams.
Thin by design. A registration, adapter, or dispatcher is supposed to be thin. Its WHAT names the callee that does the work. If you cannot name the callee, it breaks principle 1.
Worked example
A command needs to save the conversation transcript, which the core holds privately.
✗ WHAT: Returns a copy of the conversation lines.
WHERE: core/core.go.
INTERFACE: func (c *Core) Lines() []Line
returns a snapshot copy of every line; Line has Kind (one of six
constants) and Text
USE: for _, l := range c.core.Lines() {
if l.Kind != core.KindSystem { b.WriteString(render(l)) }
}
os.WriteFile(path, []byte(b.String()), 0o644)WHAT only returns state (principle 1). USE filters, renders, and writes (principle 2). Line and Kind expose the storage (principle 8). Redesign.
✓ WHAT: Writes the whole conversation so far to a file and reports where it went.
WHERE: core/core.go. The core owns the lines and their lock.
INTERFACE: func (c *Core) SaveTranscript(path string) (resolved string, err error)
path where to write; empty means "pick a default filename"
resolved the path actually written
err set when the file can't be written; resolved is then empty
USE: resolved, err := c.core.SaveTranscript(path)One job, done inside the module. The line format never escapes.
Interface width is a cost
Adding a method to an interface is a cost even when no implementation changes. An existing type that already satisfies the new method has not made the change free — it has hidden the price. What you actually changed is the set of things every caller may now do and every future implementer must provide.
Price it by asking what the widest caller can now reach, not how many lines you edited.
Deletion test. Delete the module. If complexity vanishes, it was a pass-through and should not exist. If it reappears in N callers, it earned its place.
Seams
A seam is a place you can change behavior without editing in that place.
Introduce one only when something actually varies across it. The test counts as the second adapter when — and only when — the dependency is I/O, a clock, randomness, or the network. For those, "only one caller exists" is not a reason to skip the seam; the test is the other caller, and without the seam the test reaches around the module instead of through it.
For anything else — pure computation, in-process state — one caller means no seam. Call it directly.
Relationship to coding-discipline
coding-discipline minimizes what you build. This skill constrains what you expose.
They pull in opposite directions exactly once, and this is the case that matters:
The shallow design is almost always the smaller diff.
When the two conflict, minimality applies to the implementation, never to the caller's required knowledge. "Could a senior cut this in half?" is a question about the implementation. A smaller diff that widens an interface is not the smaller change.
coding-discipline's speculative-complexity test — does a second caller exist right now? —
governs parameters, flags, and configuration. It does not govern seams at I/O boundaries;
the Seams rule above does.
Rationalizations
| Excuse | Reality |
| "The type already has this method, so the interface change is free" | You changed what every caller may do and every implementer must provide. Zero diff, real cost. |
| "The core still owns the state" | It owns the field. If callers read it directly, they own the representation, and you can never change it. |
| "It's still a narrow surface" | Look at USE. If the caller does the work there, it isn't narrow. |
| "I'll expose the state and let the caller decide policy" | Then policy lives in N callers. Name the second caller that wants a different policy — if there isn't one, the policy belongs inside. |
| "A type alias means nothing breaks" | Aliases hide dependency-direction changes. Which package owns the type now? |
| "One caller doesn't justify inventing an abstraction" | True for parameters. False for I/O, clocks, and randomness — the test is the second caller. |
| "It's technically breaking but the blast radius is nil" | Show the call sites that change in USE, before and after. |
| "The tests pass and the linter is clean" | Neither one can see interface width. That is why the contract exists. |
| "I'll just add a getter" | A getter does no work. WHAT says "returns". |
| "The names explain themselves" | Not to a reader who wasn't in this session. Write the sentence. |
Red Flags
- Writing a method that only returns stored state to a caller outside the module
- Moving a type into another package so an interface will compile
- Reaching for a type alias to keep existing code building
- A test that calls an unexported function, or touches a real filesystem, clock, or network
- "…so it satisfies the interface with no new code"
- Formatting or rendering a module's private representation from outside that module
- Noting a cost in your summary and then proceeding anyway
- A WHAT with "and" between two jobs
All of these mean: write the contract, and check it against the principles.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

