Skip to content

9 min read

Two skills loading at once can quietly contradict each other

On this page

At 02:40 the on-call engineer asked the agent to fix 1.4 million rows in invoices where a bad deploy had written plan_tier as the string "None" instead of NULL, and to add the plan_tier_source column the finance team needed to tell repaired rows from real ones. The agent produced a single migration file containing an ALTER TABLE ... ADD COLUMN and an UPDATE in one transaction, applied it, and it held a lock for four minutes and eleven seconds during which the invoicing service returned 503s.

The review the next morning went in circles. db-migrations had loaded, and it says in step 2 that a backfill goes in its own file and never shares a statement with a schema change. data-repair had also loaded, and it says that a repair which writes a correction and its provenance marker must be atomic, because a partial repair leaves rows that look corrected and are not, and the finance reconciliation job cannot tell the difference. Both documents were in context. Both are correct about their own subject. The run satisfies one and violates the other, and there is no fact anywhere in the transcript that says which one the agent was following.

The trace records the loading and not the arbitration

Everything the previous chapter built — the manifest, the token counts, the file dates — tells you which bodies arrived. None of it tells you what happened when they disagreed, because nothing in the system treats disagreement as an event. Two markdown files went into a context window, the model read both, and it produced text. There is no conflict record, no warning, no exit code, and no place where the resolution was made that anyone can inspect.

Compare that with the equivalent collisions elsewhere in a system, which are loud by construction. Register two tools with the same name and the SDK raises at startup. Import two modules exporting the same symbol and the language has a rule — last import wins, or it is an error — written down and identical on every run. Define the same key twice in a config file and the parser either rejects it or documents the precedence. Every one of those has a specified outcome and a place to look. Two skills that contradict each other have neither, and the outcome may differ between two runs of the same request because it is a judgement made in prose rather than a rule applied by a parser.

That is what makes this class of defect expensive out of proportion to how often it occurs. The 02:40 run cannot be called a bug in either skill. Whoever owns db-migrations will point at data-repair and be right; whoever owns data-repair will point back and also be right; and the review closes with everyone agreeing to be more careful, which changes nothing.

Three collisions that look the same from the outside

The disagreement above is between two skills, and there are two other shapes that produce the same silent outcome from different places.

Skill vs skillSkill vs always-loaded instructionSkill vs tool behaviour
ExampleBackfill separately vs repair atomicallySkill says apply directly; system prompt says never write to prod without a ticketSkill says batch at 10,000; tool hard-codes 5,000
How it surfacesA defensible run that violates one documentSporadic compliance across sessionsThe instruction appears to be ignored
Who winsHowever the model reads it that runUsually the resident text, not reliablyThe tool, always
Evidence in the traceBoth bodies present, no resolutionOnly the skill body is visible; the resident text is not in the manifestThe tool result contradicts the skill's numbers
Where to fix itScope lines and a stated precedenceRemove the overlap; resident text is policy, skills are procedureDelete the number from the skill

The third row of the third column is the only reliable outcome in the table, and it is reliable because the tool is code. A skill that states a value the tool already enforces is not a second opinion; it is documentation that will fall out of date and confuse the next reader, and the fix is to delete the number and say "the tool batches; do not set a batch size" — the seam from the first chapter, applied to a sentence rather than to a feature.

The middle column is the one people underestimate. An instruction in the always-loaded configuration and a skill body are both text asking for compliance, and when they disagree there is no mechanism that prefers either. What actually happens is influenced by recency, by phrasing, by how emphatic each one is, and by how much else is in the window, which means the same request can go one way on Monday and the other way on Thursday. Teams read that as flakiness in the model. It is a specification with two answers in it.

Say what a skill does not govern

The cheapest structural defence is a line in each skill body naming its edges. It costs two sentences and it is the thing most skill authors never write, because when you are writing a document about migrations it does not occur to you that anyone would read it while doing something else.

markdown
---
name: db-migrations
description: Use when writing, reviewing, or applying a database migration — ...
---

## Scope

This skill governs planned schema changes made ahead of time: adding or
altering columns, indexes, and their backfills and rollbacks.

It does not govern data repair during an incident. If the request is to
correct existing rows because of a defect or an outage, `data-repair` takes
precedence over every rule in this file, including the requirement that a
backfill live in its own migration.

The second paragraph is the load-bearing one, and it works because it is specific about which rule yields and to whom. A scope line that says "use judgement when other skills apply" hands the problem straight back to the model. Naming the other skill, and naming the rule that gets suspended, means a reader of either document can predict the 02:40 run, which is the same reviewability property the third chapter asked for, extended to the boundary between documents.

The corresponding line in data-repair points the other way — "this skill covers correcting existing rows during or after an incident; for planned schema changes see db-migrations, whose rules apply once the incident is closed" — and the pair of them together is what makes the boundary a shared fact rather than two private opinions. Writing only one side is a common half-measure and it leaves the other document still claiming universal authority.

Decide the tiebreak before you need it

Scope lines handle the overlaps somebody anticipated. For the rest, the team needs a precedence order recorded once and referenced from every skill, so that a conflict nobody foresaw has a defined answer instead of a coin flip.

markdown
## Precedence (skills/PRECEDENCE.md)

When two loaded skills conflict, the higher tier wins outright:

1. Safety and compliance skills (`data-retention`, `pii-handling`)
2. Active-incident skills (`incident-response`, `data-repair`)
3. Domain skills (`db-migrations`, `platform-api`, `code-review`)
4. Style and convention skills

Within a tier, do not choose. Say that the request is covered by two skills
that disagree, quote the conflicting sentences, state which one you are
following and why, and continue. If the conflict makes the request unsafe to
execute, stop and ask.

The instruction to surface the conflict is worth more than the ordering above it. An unattributable run is the actual injury in this chapter, and a run that says "these two rules disagree, here is what I did" converts it into an ordinary review. It also produces the data you need: every surfaced conflict is a pair of documents somebody should give a scope line to, and the frequency tells you which pairs matter.

text
assistant: Two loaded skills disagree here.
  db-migrations step 2: "Put the backfill in its own migration file. Do not
    combine the two."
  data-repair §Atomicity: "Write the correction and its provenance marker in
    one transaction."
  data-repair is tier 2 (active incident), db-migrations is tier 3, so I am
  following data-repair: one file, both statements, single transaction.
  This will hold an ACCESS EXCLUSIVE lock on invoices for an estimated 4 min
  at 1.4M rows. Confirm before I apply.

That turn is what the 02:40 run should have looked like. The outcome is identical — the same SQL, the same lock — and the engineer gets the four minutes before the 503s rather than after, which is the whole difference between an incident and a decision.

The tempting fix is to merge the two documents

Faced with the review that went in circles, the natural proposal is to put migrations and repair in one skill so the rules cannot contradict each other. It does eliminate the collision, and it relocates the problem: the contradiction is now inside a single document, where it becomes an ordinary editing task and the author has to decide, once, what happens when a repair needs a schema change. That is genuine progress on this pair.

It does not generalise, because the merge argument applies to every pair, and following it produces one skill covering everything. The costs of that shape are specific and are the subject of the next chapter. The middle position is that documents get merged when they are about the same task and separated when they are not, and the test is whether a reader can state the boundary in one sentence. Migrations and incident data repair are different tasks with a shared mechanism, so they stay separate with scope lines. Migrations and rollbacks are one task described in two places, and those should never have been two files.

Which pairs are worth a scope line

You cannot write boundaries for every combination, and you do not need to, because co-loading is not uniformly distributed. The manifests from the previous chapter already record which skills entered context together, so counting pairs over a few weeks of sessions tells you where the risk actually is.

bash
jq -r '[.loaded[] | select(.tokens > 0) | .path
        | capture("skills/(?<s>[^/]+)/").s] | unique
       | combinations(2) | @tsv' manifests/*.json \
  | sort | uniq -c | sort -rn | head

On the directory this chapter comes from, that produced 41 co-loads of db-migrations with data-repair, 38 of platform-api with code-review, 12 of db-migrations with code-review, and a long tail of pairs that had happened once or twice. Two pairs account for most of the exposure, and both were written by different people who had never read each other's document.

The count is a prompt to look, not a verdict. platform-api and code-review co-load constantly and have nothing to say about each other, so their boundary is uncontroversial and needs no line. The pair worth an afternoon is the one where the counts are high and the two documents both give procedures for the same object — rows in a table, a deployment, an incident channel — because that is where the same run can satisfy one and violate the other. Reading the two bodies side by side with that question in mind takes twenty minutes per pair and either produces a scope line or confirms there is nothing to write.

What this does not solve

Precedence tiers and scope lines reduce the frequency and do not eliminate the class. The number of pairs grows quadratically — six skills give fifteen pairs, twelve give sixty-six — and nobody is going to write a scope line for each, so most conflicts will still be discovered by encountering them. The realistic position is that the tiebreak makes the unanticipated case survivable and the surfacing rule makes it visible, and the residue is caught by sampling traces rather than by design.

There is also a limit on what surfacing can detect. A conflict the model does not notice does not get surfaced, and models notice contradictions between two adjacent explicit rules far more readily than they notice a contradiction between a rule and an implication three paragraphs into another document. Nothing in this chapter turns skills into a system with defined semantics; it turns a silent resolution into an announced one most of the time, which is a large improvement over what the 02:40 review had and is not a guarantee.

The version of this problem that arrives without any of the drama is not two teams disagreeing at all. It is one skill that grew until it covered six subjects, loading its on-call etiquette section every time someone asks about a migration, and the argument for splitting it runs directly into everything this chapter just described.

Agent skills — read the current version at https://daviddominguez.dev/learn/en/agent-skills/two-skills-loading-at-once-can-quietly-contradict-each-other

Back to top