I rewrote the same skill file three times last month. Each time I changed the explanation. The trigger conditions never changed. I’d baked them into the body so deeply that touching the explanation meant re-typing the trigger from memory, which meant the trigger drifted, which meant the skill fired in places it shouldn’t, which meant a fourth rewrite.
The trigger was right the first time. The body wasn’t. I rewrote the wrong half of the file three times because the two halves weren’t separable.
That afternoon I went back through every skill file, slash command, and harness rule I’d written in the last quarter and looked for the same shape. It was everywhere. The contract — what the artifact must accomplish — was tangled together with the prose explaining how to do it. I couldn’t change one without rewriting the other. The seam wasn’t there.
This is the ports-and-adapters pattern, applied to prose.
The pattern, translated
Ports and adapters comes from software architecture. You define an interface — the port — and write multiple implementations behind it — the adapters. The interface is stable. The implementations swap freely. The point is the seam: code on one side of it doesn’t know or care about the code on the other side.
The same shape applies to the prose we write for LLMs. A prompt, a skill file, an agent definition, a harness rule — each one has two parts authors usually conflate.
The port is what the prose must accomplish. The preconditions, the inputs, the outputs, the failure modes, what counts as satisfied, what counts as out of scope. The contract the prose has to honor, stated independently of how you’d explain it.
The adapter is the actual wording. The metaphors, the worked examples, the tone, the level of detail. One specific way to satisfy the port.
Most prose-for-LLMs collapses these. You sit down to write a skill or a system prompt and what comes out is a tangle of “what this does” with “how I’m explaining it.” Six months later you want to change the explanation without changing the contract, or vice versa. The seam isn’t there, so you rewrite the whole thing.
The pattern is to separate them at the point of authoring. Write the port explicitly first — a short list of inputs, outputs, invariants, failure modes. Then write the adapter as one specific way to satisfy the port. Multiple adapters can satisfy the same port: terse, verbose, different audience, different model.
What a port looks like, written down
Here is a port for a skill file that decides whether a code review should run. I write the port as a small block before I write the body.
port: review-decision
triggers:
- user asks "review this", "look at the diff", "/review"
- PR is open and unreviewed
preconditions:
- a diff exists against a base ref
- the diff is not empty
outputs:
- a list of findings, each with a file path and line
- a verdict -> ok | suggest-changes | block
invariants:
- never edits the diff
- never claims pass without evidence
out-of-scope:
- running tests
- refactoring adjacent code
That’s the port. It’s twelve lines and it says nothing about tone, examples, metaphors, or the model on the other end. It is, deliberately, boring.
The first time I wrote a port this way I felt like I was missing something. The contract is so spare. There’s nothing in it to chew on. Then I tried writing two adapters against it and the spareness paid for itself.
Two adapters, same port
Here’s adapter A — terse, for a model that infers well from short instructions:
Review the diff against base. For each file, list findings as path:line — issue.
Verdict: ok / suggest-changes / block. Don’t edit. Don’t run tests. Show evidence for any “block.”
Here’s adapter B — verbose, for a model that benefits from worked examples:
You’re reviewing a diff against a base ref. Walk through each changed file. For
every issue you find, write one line: the file path, the line number, then a
short description of the problem.End with a verdict: “ok” if nothing meaningful, “suggest-changes” for findings
that aren’t blockers, “block” for findings that should stop the merge.You won’t edit the diff. You won’t run tests. If you call something a blocker,
quote the line that proves it.Example finding:
src/auth.ts:42 — token compared with ==, allows type coercion
Both adapters honor the same port. The findings have a path and a line. The verdict is one of the three values. The invariants — no editing, no test-running, evidence for blocks — survive in both. The difference between them is texture, not contract.
Now: which one do I edit when I want to change the tone? Adapter A or B. Which one do I edit when I want to change what “blocker” means? The port, and then both adapters fall out of date in a way I can see. The seam tells me where to touch.
Where the seam shows up in real harness work
Once I started looking, the port-adapter split was hiding in every artifact type I had.
Skill files. A skill has triggers — the conditions under which it fires — and a body — what to do once it fires. The triggers are the port. The body is the adapter. I had been writing both in one stream of prose, with the trigger conditions described inline (“when the user asks about X, do Y”). Splitting them out — frontmatter or top-of-file list for triggers, body for behavior — meant I could change the body without touching the trigger, and rewrite the trigger without disturbing the body. The skill files I rewrote three times stopped needing rewrites.
Slash commands. The frontmatter — description, argument hints, required tools — is part of the port. The body is the adapter explaining how to behave. The frontmatter is what the harness reads to decide whether to surface the command; the body is what the model reads once it’s invoked. Those are two different readers, and the seam respects that.
Agent definitions. The tools list is a port: what capabilities does this agent have. The system prompt has both port content (invariants the agent must hold) and adapter content (worked examples, voice, the way you’d explain the job to a new hire). The most useful thing I did with my agent files was move the invariants to the top in a small list, separate from the worked examples below. The invariants stopped getting accidentally edited when I improved an example.
Harness rules. A rule has a scope line — when this rule applies, which paths it covers — and a body — what to do when in scope. Scope is the port. Body is the adapter. I had been writing rules with the scope baked into the body (“when working in the API layer, never…”). Pulling the scope into a header line meant the rule could be rewritten without re-deriving the scope, and the harness could decide whether to load the rule without reading the body.
Intent contracts. In an intent-driven workflow, the acceptance criteria are the port for “done.” The body of the contract — the explanation, the worked context, the references — is the adapter. The acceptance is what a verifier reads. The body is what the executor reads. Two readers, two purposes, one seam.
One port, many adapters. The port is the expensive part. The adapters are cheap.
Why this pays off
The argument for the pattern is the second-draft argument. First drafts get slower. You’re writing the port explicitly, which feels like overhead. You’d write the body faster without it.
Second drafts get faster. You’re changing the explanation without re-deriving the contract, or rewriting the contract without disturbing the explanation that already works. The seam means each edit touches one side at a time.
Most prose-for-LLMs gets edited more than it gets written. A skill file you author in fifteen minutes you’ll edit twenty times over its life. A system prompt you draft in an hour you’ll tune for months. The cumulative cost is in the edits. The seam lowers the cost of every edit after the first.
The other argument is the multiple-readers argument. Prose-for-LLMs has more readers than people realize.
You, today, writing it.
You, in six months, having forgotten what it does.
Another engineer who inherits it.
A different model than the one you wrote it against — first Sonnet, then Opus, Fable for a few days, and then back to Opus — something smaller for cost reasons next quarter.
Each reader wants a different adapter against the same port. The terse adapter works for the smart model and the engineer who already knows the domain. The verbose adapter works for the smaller model and the engineer who’s new to the area. Without a port, you can’t write a second adapter; you have to rewrite the whole thing, and you’ll get the contract subtly wrong on the rewrite.
With a port, you write one adapter for each reader you care about. They all satisfy the same contract. They diverge in texture and they agree in substance.
How to tell if your seam is real
Two tests. Both are cheap to run.
The first test: rewrite the adapter without changing what’s true. Take your skill file, your prompt, your rule. Cut its length in half, or double it. Change the metaphors. Move from second person to first person. Now read the port and ask: is anything in the port newly out of date? If yes, you weren’t actually working only on the adapter — adapter territory was leaking into the port. If no, the seam held.
The second test: rewrite the port and watch the adapter fall out of date. Change an input. Change a failure mode. Add an invariant. Look at the adapter and find the parts that now contradict the port. The places the adapter contradicts the port are the places where the seam was leaking the other way — the adapter was specifying contract details. If the adapter falls out of date in a clean, visible way, the seam is real. If you can’t tell which parts of the adapter to update, the seam isn’t really there yet.
The failure mode to watch for is ports that try to specify the adapter. “Must use a friendly tone.” “Must include three examples.” “Must explain by analogy.” Those are adapter concerns dressed up as contract. They belong in the adapter, where they can vary. The port is what’s true; the adapter is how it sounds. Mixing those up is what got you into the tangle in the first place.
The other failure mode is adapters that try to redefine the port. The adapter that says “actually, blockers can also mean stylistic issues if the team agrees” is rewriting the contract from inside the explanation. You won’t see it until two adapters drift apart and you can’t tell which one is right.
Where the pattern doesn’t fit
The pattern is overhead. For one-off prompts you’ll use once and discard, skip it. For short scripts you’re throwing away after a single run, skip it. The seam pays off over edits, and prose you’ll never edit doesn’t need a seam.
The other place to be careful: when the port is genuinely entangled with the adapter — when the way you explain something is part of what you’re trying to do. Writing about tone, voice, style, examples-as-pedagogy. There the adapter is the contract. Forcing a separation is theater.
For everything else — skills, slash commands, agent definitions, harness rules, prompts you’ll tune over time, contracts that other readers will inherit — the pattern earns its keep within a week.
Write your next prompt against an interface
Pick one piece of prose-for-LLMs you’ve edited more than twice. A skill file, a system prompt, a rule, whichever.
Open it and write the port at the top. Eight to twelve lines. Inputs, outputs, invariants, failure modes, out-of-scope. Don’t worry about wording the body yet — just write down what’s true about the contract.
Read the existing body against the new port. Find the parts of the body that are stating contract details (“only fires when X”) and move them up to the port. Find the parts of the port you accidentally wrote that are really about tone or examples (“respond in a friendly voice”) and move them down to the body. Be honest about which side each line belongs on.
Now do the second test. Make a small change to the port — add an invariant, tighten an input. Watch the body. The parts of the body that contradict the new port are the parts where the seam was leaking. Fix them. Repeat once.
Then leave the file alone for a week. The next time you edit it, notice whether you can change the body without re-deriving the contract, or the port without disturbing the body. If yes, the seam is doing its work. If no, the seam isn’t real yet — find the leak and fix it.
The artifacts I rewrote three times don’t get rewritten three times anymore. The ports are stable. The adapters change as I learn what works. That’s the whole shape of it.
For further actions, you may consider blocking this person and/or reporting abuse
