In the previous article (you can read it here here), I wrote about why AI hallucinations are a particularly uncomfortable problem in software development.

The annoying conclusion was that we cannot simply prompt our way out of them.

“Do not hallucinate” is about as useful as putting “do not introduce bugs” in the acceptance criteria.

So what do we actually do?

In 2026, I think the answer is becoming clearer.

We should stop treating the coding model as something that is supposed to know our system, and start designing an environment where it can find the right information when it needs it.

That is a slightly different problem.

And it has much more to do with system design than prompt engineering.

The real problem is missing context

Imagine asking GitHub Copilot:

Add support for cancelling an order.

Sounds simple.

Except Copilot needs to know quite a few things.

Can shipped orders be cancelled?

Does cancellation refund a payment?

Do we release inventory?

Where does the business rule live?

What does a failure look like in this application?

Are we using controllers or Minimal APIs?

How do we test this?

If those answers are not available, the model still has to produce something.

And models are very good at producing things that look reasonable.

That was the problem from the previous article.

So instead of asking:

How do we make the model smarter?

A more useful question might be:

How do we make guessing unnecessary?

That takes us from prompt engineering to context engineering.

Context is not one giant instruction file

The first temptation is obvious.

GitHub Copilot supports repository instructions, so let’s create:

.github/copilot-instructions.md

and put everything we know about the application in there.

Architecture.

Business rules.

Database conventions.

Deployment procedures.

API documentation.

The story of that one production incident nobody wants to discuss.

A few months later we have a 5,000-line Markdown file that nobody maintains.

We have successfully reinvented the enterprise wiki.

Except now we inject it into an LLM.

There is a better distinction.

I think about AI context in roughly six buckets:

Instructions       → How should you behave here?

Skills             → How do we perform this kind of task?

Repository/docs    → How does the system actually work?

Retrieval/Spaces   → What existing knowledge is relevant now?

Tools/MCP          → What is true outside the repository right now?

Build/tests        → Is the generated result actually correct?
Simple diagram showing Copilot in the center, with instructions, skills, code and docs, and tools providing context, followed by build and test validation.

Those are different questions.

They deserve different answers.

1. Instructions: the rules of the road

Instructions are good for stable rules that apply to a lot of development work.

GitHub Copilot supports repository instructions and path-specific instructions, so guidance can be applied globally or only where it is relevant.

For example:

# Architecture

- We use vertical slices.
- Domain must not reference Infrastructure.
- HTTP endpoints contain no business logic.
- Prefer existing abstractions over creating new ones.
- Do not add NuGet packages unless the task requires it.

# Validation

- Run dotnet build after backend changes.
- Run the relevant tests.
- Do not modify tests just to make generated code pass.

These are reasonable instructions because they describe how we work.

But this would not be:

Orders can only be cancelled while Pending.
Premium customers receive a 12% discount.
Invoices over €10,000 require manual approval.

Those are business rules.

They should have a better home.

2. Domain knowledge: put truth in the system

Suppose this is our cancellation rule:

public Result Cancel()
{
    if (Status != OrderStatus.Pending)
    {
        return OrderErrors.CannotCancel(Status);
    }

    Status = OrderStatus.Cancelled;

    return Result.Success();
}

That is excellent context for Copilot.

Even better if we have:

[Fact]
public void Shipped_order_cannot_be_cancelled()
{
    var order = OrderMother.Shipped();

    var result = order.Cancel();

    result.IsFailure.Should().BeTrue();
}

Now the rule exists somewhere authoritative.

The model does not need a special AI instruction telling it how cancellation works.

It can find the implementation and tests.

This is an important principle:

If humans need a fact to understand the system, that fact should not live exclusively in AI configuration.

Business knowledge belongs in code, tests, configuration, ADRs or documentation.

Copilot should retrieve it, not own it.

3. Skills: teach procedures, not facts

Now imagine something different:

How do we create an Entity Framework Core migration in this repository?

That is not really an architecture rule.

And it is not a business fact.

It is a procedure.

This is where Agent Skills become interesting.

GitHub Copilot’s Agent Skills are folders containing instructions, scripts and resources that Copilot can load when relevant to a specialized task.

For example:

.github/
    skills/
        ef-core-migration/
            SKILL.md

with something like:

---
name: ef-core-migration
description: Use when creating or modifying an EF Core migration.
---

# Procedure

Before creating a migration:

1. Identify the affected DbContext.
2. Find the existing entity configuration.
3. Check whether the schema change is backwards compatible.

After generating it:

1. Inspect Up().
2. Inspect Down().
3. Check for unrelated schema changes.
4. Run the database integration tests.

Never manually invent migration metadata when EF Core can generate it.

That knowledge only needs to appear when we are doing migration work.

Which is much better than injecting migration procedures while Copilot is trying to fix an HTTP endpoint.

The rough rule I use is:

“We always…” usually belongs in instructions.

“When doing X, follow these steps…” probably belongs in a skill.

4. Retrieval: let the codebase answer questions

Now we get to the part people often call RAG.

The basic idea is simple:

Question
   ↓
Find relevant information
   ↓
Put it into context
   ↓
Generate an answer

GitHub Copilot can use semantic code search and repository indexing to locate relevant code instead of relying only on whatever happens to be visible in the current file.

For our cancellation feature, useful context might be:

Order.cs
OrderStatus.cs
CancelOrderHandler.cs
CancelOrderTests.cs
OrderErrors.cs
docs/order-lifecycle.md

We do not want to copy all of that into our instructions.

We want retrieval to find it when it matters.

And this has an interesting consequence.

Software architecture affects retrieval quality.

Compare:

Helpers.cs
CommonService.cs
OrderManager.cs
OrderManager2.cs
Utils.cs

with:

Orders/
    CancelOrder/
        CancelOrderCommand.cs
        CancelOrderHandler.cs
        CancelOrderTests.cs

Domain/
    Orders/
        Order.cs
        OrderStatus.cs
        OrderErrors.cs

Which one would you rather ask an AI agent to understand?

Probably the same one you would rather give to a new developer.

Apparently naming things properly survived the AI revolution.

5. Curated context: sometimes the answer spans repositories

Retrieval from one repository is not always enough.

Imagine the payment flow involves:

checkout-api
payments-service
shared-contracts

refund-policy.md
ADR-014-payment-retries.md
Incident-481

That is where curated context becomes useful.

Copilot Spaces can collect repositories, code, issues, pull requests, notes and other material, and Copilot can answer questions grounded in the context of that Space.

Conceptually:

Payments Space

├── checkout-api
├── payments-service
├── shared-contracts
├── refund-policy.md
└── ADR-014

This is much closer to how I think teams should use RAG-style techniques.

Not:

Give the model everything.

But:

Give retrieval a useful body of knowledge to search.

More context is not automatically better.

Relevant context is better.

6. Tools: ask the source instead of guessing

Some knowledge should not live in the repository at all.

For example:

Which version is running in production?

What does the current Payments OpenAPI schema contain?

Is feature flag new-refund-flow enabled?

What errors occurred in the last hour?

That information changes.

Putting it in copilot-instructions.md would be impressively wrong almost immediately.

This is where tools and MCP become useful.

GitHub Copilot supports MCP integrations that can give Copilot access to external systems, data sources and services.

Conceptually, we might expose tools such as:

get_api_schema("payments")

get_feature_flag("new-refund-flow")

get_service_version("payments", "production")

search_logs("payments", "RefundFailed")

Now consider our AI generating this:

await client.PostAsJsonAsync(
    $"/payments/{paymentId}/refund",
    request);

It looks plausible.

But maybe that endpoint does not exist.

Instead of relying on the model’s knowledge of how payment APIs usually work, the agent can query the actual contract.

That’s the important shift.

Do not ask the model to remember something when you can give it a way to look it up.

And finally: do not ask AI questions that .NET can answer

Even with perfect context, generated code can still be wrong.

Fortunately, we already have some remarkably reliable AI tools.

They are called:

dotnet build
dotnet test
Roslyn analyzers
nullable reference types
architecture tests
integration tests

If the question is:

Does this compile?

Do not ask Copilot.

Run the compiler.

If the question is:

Does the cancellation rule still work?

Run the test.

If the question is:

Did Domain accidentally gain a reference to Infrastructure?

Make that an architecture test.

A probabilistic system should not be the final authority for something a deterministic system can prove.

So the complete loop becomes:

Instructions
     ↓
Retrieve relevant knowledge
     ↓
Load a relevant skill
     ↓
Query external tools if necessary
     ↓
Generate
     ↓
Build
     ↓
Test
     ↓
Human review

And I think this is where the system-design conversation around AI gets interesting.

We cannot eliminate hallucinations.

But we can design the development environment so the model has fewer reasons to hallucinate in the first place.

Stable rules go into instructions.

Procedures go into skills.

Business truth stays in the system.

Relevant knowledge is retrieved when needed.

Changing external truth comes from tools.

And correctness is checked by deterministic systems wherever possible.

Prompt engineering asks:

What should I tell the model?

Context engineering asks a much more useful question:

Where should the truth live, and how does the model get to it?

That feels considerably less magical.

And considerably more like software engineering.

Sources and further reading