Skip to content

AI Coding Agents Explained

Lesson 1 of 10Intermediate13 min readGitHub Copilot & AI Engineering · AI AgentsVerified: GitHub Copilot coding agent, agent mode and Copilot CLI documentation, September 2026

An agent is a model in a loop with tools.

That is the whole definition, and it is worth starting there because the word carries a lot of freight it does not deserve. There is no autonomy in the philosophical sense, no goal-seeking beyond the prompt, and no capability beyond the tools it was given. An agent that cannot run git push cannot push, regardless of what it decides.

What is genuinely new is the loop. A model that answers a question produces text. A model that can run a command, read the output, and act on it can make progress on a task over many steps — including recovering from its own mistakes, which is the part that makes delegation viable at all.

Every agent runs the same cycle:

  1. Read the task and whatever context it has.
  2. Decide on an action — read a file, run a command, edit code, search.
  3. Execute it, if permitted.
  4. Read the result.
  5. Repeat until the task looks done or a limit is hit.

Two properties of that cycle explain most agent behaviour.

It is self-correcting within a session. A test fails, the agent reads the failure, changes the code, runs it again. That is why an agent can complete a task no single generation would have got right, and it is the strongest argument for giving an agent a way to verify its own work.

Everything it can do is in step three. The tool list is the boundary. Not the prompt, not the instructions, not the model’s judgement — the enumerated set of things it is permitted to execute. This is the single most important fact in the cluster and the one most often glossed over.

Four things, in decreasing order of reliability.

Tools. What it can execute. Deterministic, enumerable, enforced by the harness rather than by the model. An agent without a network tool cannot make a network call.

Permissions. What its identity is allowed to do. A token with read-only repository scope produces read-only outcomes regardless of what the agent attempts.

The environment. What exists to be acted on. A container with no cloud credentials cannot touch cloud infrastructure. An ephemeral environment discards its own state.

Instructions. What you asked for and what the repository’s guidance says. Influential, not enforcing. An instruction saying “do not modify tests” is a strong hint and not a control.

The ordering is the lesson. Tools, permissions and environment are boundaries. Instructions are preferences. Every safe agent architecture puts the things that matter in the first three and uses the fourth for everything else.

They are genuinely different systems, and the documentation treats them as distinct. Using the names interchangeably causes real confusion about what is running where.

Agent modeCopilot CLICopilot cloud agent
Where it runsYour IDEYour terminalGitHub-hosted environment
On whose machineYoursYoursGitHub’s
SupervisionYou watch and approveYou watch and approveAsynchronous
Credentials in reachWhatever your machine hasWhatever your shell hasWhatever the environment was given
OutputEdits in your working treeEdits and commands locallyA pull request
Typical sessionMinutes, interactiveMinutes, interactiveUp to the platform limit

Agent mode is the supervised loop inside VS Code or a JetBrains IDE. You see each edit, approve commands, and stop it when it goes wrong. Covered in agent mode.

Copilot CLI is the same shape in a terminal, with the shell as the primary tool. Covered across the Copilot CLI cluster.

The Copilot cloud agent runs on GitHub’s infrastructure, works from an issue or a task, and opens a pull request. Nobody watches it work. Covered in the Copilot cloud agent.

The distinction that actually matters, and it cuts across the surfaces.

Supervised means a human is present while the work happens. Mistakes are caught in seconds. The controls are attention and the approval prompt.

Delegated means nobody is watching. Mistakes are caught at review, if at all. The controls are scope before and review after.

Everything gets harder when you cross that line:

Scope has to be right up front. You cannot narrow it halfway through.

Untrusted input becomes reachable. A delegated agent reads issues and comments written by people who are not you.

Auditability stops being optional. “What did it do” has to be answerable from logs and commits.

The output needs more scepticism, not less. Nobody watched, so the pull request is the entire evidence.

The mistake teams make is treating delegation as a scaling-up of supervision. It is a different engineering problem with different controls, and the transition deserves to be deliberate.

A recurring source of surprise. An agent does not know your repository. It reads parts of it, and which parts is a consequence of how the harness works rather than of what would be ideal.

What it reliably has:

Your prompt or the issue text. The whole of it, and it carries more weight than anything else.

Repository instruction files. AGENTS.md, .github/copilot-instructions.md and path-scoped instructions, where the surface supports them. This is the mechanism for telling it things it could not otherwise know.

Files it opened. It searches, reads, and accumulates what it read. A large repository means it has read a small fraction of it.

Command output it ran. Test failures, build errors, git log. This is often better context than the source, because it is specific and current.

What it does not have, and people assume it does:

The rest of the repository. Unread files are not in context. A convention that exists in forty places it did not open is a convention it does not know about.

Your history of decisions. Why the previous approach was abandoned. What broke last year. Which module the team considers untouchable.

Anything in a wiki, a ticket system or a chat. Unless a tool reaches it, it does not exist.

What other agents did. Sessions are independent. Two agents working on related tasks do not coordinate.

The practical consequence: the highest-leverage thing you can do for agent output quality is not a better prompt — it is writing down the context in a place the agent reads. An AGENTS.md describing the stack, the build commands, the conventions and the things not to touch changes agent behaviour more than any amount of prompt refinement, and it helps every session rather than one. See repository agent configuration.

The task profile matters more than the tooling.

Good candidates:

Well-specified and bounded. “Add pagination to the /users endpoint, matching the pattern in /orders.” A clear target and an existing example.

Verifiable. There is a test, a type check, a build — something that fails if it went wrong. The self-correcting loop needs a signal, and tasks without one produce confident output nobody can check.

Mechanically repetitive. Applying the same change across forty files. Tedious for a person, and exactly what a tireless loop is for.

Low blast radius. If it is wrong, a review catches it and nothing is lost but time.

Already understood by you. The tasks that go best are ones you could do yourself and would rather not. You can specify them precisely and you can tell immediately whether the output is right — both of which fail when you delegate something you do not understand.

Poor candidates:

Underspecified. “Improve error handling.” An agent will do something. Whether it is what you wanted is a coin flip, and you will spend longer reviewing than writing.

Architecturally consequential. Choosing between a queue and a cron job is a decision with context that lives in people. Delegating it does not produce a wrong answer so much as an ungrounded one.

Unverifiable. No tests, no types, behaviour only observable in production.

High blast radius. Anything touching authentication, payments, migrations or infrastructure state, where a missed defect costs far more than the delegation saved. Not because an agent cannot write it, but because the cost of a missed defect is asymmetric.

The heuristic that captures most of it: if you could write a precise issue for a competent contractor who does not know your team, it is delegable. If the issue would need a conversation, it is not — and the conversation is the part that cannot be delegated.

The most important engineering point in the cluster.

An agent completing a task successfully means the loop terminated. It does not mean the code is correct. Those are different claims, and conflating them is the failure mode that produces the worst outcomes in this entire pillar.

What “it worked” actually establishes:

The agent reportsThis establishes
“Task complete”The loop ended
“Tests pass”The tests it ran, passed
“Build succeeds”It compiles
“I fixed the bug”It changed code it believed was the bug

None of those is “the change is correct”. The gap is filled by the same chain everything else in this pillar rests on:

AI output → compile/validate → tests → static analysis → security scan → human review

Each stage catches a different class of problem, and none of them is optional because an agent said the task was done.

Knowing the failure shapes is what lets you recognise them in a pull request, and they are consistent enough to enumerate.

Scope drift. Asked to fix a bug, it also reformats the file, renames two variables and updates an unrelated import. The fix is correct; the diff is four times larger than it should be and the review cost went up accordingly. Common, benign, and the most frequent thing you will actually see.

Solving the wrong problem. It addressed a plausible reading of the task that was not yours. The code is fine. The task is not done. This is what underspecification produces, and it is why the issue text matters more than any other input.

Fixing the symptom. A test fails, so the test is changed. An error appears, so it is caught and swallowed. The loop optimises for the observable signal, and “the test now passes” is observable in a way that “the underlying behaviour is correct” is not.

Confident incompleteness. It reports the task done having handled the main path and not the error paths, the concurrency case, or the migration. The completion message does not distinguish between “finished” and “stopped”.

Plausible invention. A call to a function that does not exist, a configuration key that was never a key, an API shaped the way that API usually is. Compilation and tests catch most of this, which is exactly why unverifiable tasks are the dangerous ones.

Stalling. It hits a genuine blocker — a missing credential, a network restriction, a failing dependency — and either loops on it or reports partial progress. Not a defect; a limit being enforced correctly.

Cascade from a wrong early step. It misread something in step two and everything after is consistent with the misreading. Internally coherent, entirely wrong, and the hardest of these to spot because nothing looks broken.

Reading a pull request with these in mind is much faster than reading it cold. The two to check first are scope drift, because it is visible in the file list before you read anything, and symptom-fixing, because it is visible in the test diff.

The design property that makes delegation acceptable: an agent’s output is a pull request.

That is not a small detail. It means agent work enters at the point your repository is already sceptical. Branch protection applies. Required checks run. CODEOWNERS requests the right people. The merge needs an approving human review.

Nothing about delegation requires you to weaken any of that, and every serious problem in this area comes from having weakened it — usually by giving an agent identity permissions that bypass a control that would otherwise have held.

The rule: an agent should have strictly fewer permissions than the humans on the repository, and absolutely never the ability to merge to a protected branch, alter branch protection, or dismiss reviews.

Task quality bounds everything downstream, and it is the input you have most control over.

A weak task:

Fix the bug where users sometimes see stale data.

The same task, written to be delegable:

Bug: GET /api/users/:id returns stale data for up to 60s after a PATCH.
Cause (confirmed): the cache in src/cache/user-cache.ts is not invalidated
by the update path in src/services/user-service.ts.
Fix: invalidate the cache entry in updateUser() after a successful write.
Follow the pattern already used in order-service.ts:changeStatus().
Verify: add a test to src/services/user-service.test.ts asserting that a
read after an update returns the new value. The existing test file has a
fixture for this.
Out of scope: cache configuration, TTL changes, other services.

Five components, each doing specific work:

The observable behaviour. What is wrong, precisely enough to recognise when it is right.

The cause, if you know it. Withholding it to “see if the agent finds it” wastes a session and usually produces a fix in the wrong layer. If you know, say so.

The intended fix and an existing pattern to follow. A pointer to working code in the same repository is worth more than a paragraph of description — it is context the agent will actually read.

How it will be verified. This gives the loop its correction signal, which is the difference between self-correcting and confidently wrong.

What is out of scope. The single most effective line against scope drift.

That takes five minutes to write, and it is five minutes that would have gone into reviewing a confused pull request instead. The economics are unambiguous for anything non-trivial: time spent specifying is recovered at review, and time not spent specifying is paid there at a worse rate.

The corollary is worth stating plainly: if writing the task properly would take longer than doing the work, do the work. Delegation is not free, and the break-even point is further out than enthusiasm suggests.

Believing instructions are controls. Tools, permissions and the environment enforce. Instructions influence.

Delegating underspecified work. The output will be plausible and unrelated to what you wanted.

Treating completion as correctness. The loop ended. That is all.

Using the surface names interchangeably. Agent mode, the CLI and the cloud agent differ in where they run, what they can reach, and who is watching.

Giving an agent broad credentials because it was easier. The permission is the boundary; making it wide removes the boundary.

Reviewing agent pull requests less carefully because CI is green. Nobody watched the work; the pull request is the entire evidence.

Delegating a task with no verification signal. The loop has nothing to correct against.

An agent is a contractor with exactly the keys you handed over.

It will work diligently on what you described, using what it can reach, and stop when it thinks it is done. It has no independent knowledge of your organisation, no stake in the outcome, and no ability to exceed the access you granted.

That framing answers most design questions. Would you give this contractor production credentials? Would you let them merge without review? Would you hand them a two-sentence brief and expect the right result? The answers are the same for an agent, and they are the answers this cluster spends the rest of its lessons implementing.

  • An agent is a model in a loop with tools; the tool list is the boundary
  • Tools, permissions and environment enforce — instructions only influence
  • Agent mode, Copilot CLI and the cloud agent are distinct systems, not synonyms
  • Supervised and delegated work need different controls: attention versus scope-and-review
  • Delegable tasks are well-specified, verifiable, repetitive and low-blast-radius
  • Completion means the loop ended, never that the output is correct
  • Agent output arrives as a pull request, which is why existing repository controls still apply

Use a disposable repository. No production credentials.

  1. Give an agent a well-specified, verifiable task — add a function matching an existing pattern, with a test. Note how many steps the loop took.

  2. Give it a deliberately underspecified task — “improve the error handling in this module”. Compare the output to what you actually wanted.

  3. Give it a task with no test signal at all. Predict: how confident is the completion message relative to the evidence?

  4. Ask it to write both an implementation and its tests. Read the tests against the requirement, not against the code. Predict: do they test the requirement?

  5. Restrict the tool list so it cannot run the test command. Repeat task 1. Note how the loop’s behaviour changes without a verification signal.

  6. Try the same task in a supervised surface and a delegated one. Note what you learned by watching that the pull request alone would not have told you.

  7. Delete the repository.

AI-assisted engineering learning pathEleven lessons on getting value from Copilot and agents without giving up review discipline.