System instructions
AGENTS.md is the most-impactful customization surface in core-agent. The model sees it on every turn. Get it right and the agent behaves consistently across operators and across sessions; get it wrong and the agent acts unpredictably regardless of what skills or tools you wire up.
This page covers the patterns that work, the failure modes to watch for, and how to iterate on an AGENTS.md instead of writing it once and hoping.
For the basic mechanics (file location, fallback chain, schema) see Interactive quickstart → Step 1. This page is the prescriptive how-do-I-write-a-good-one companion.
Mental model: the layer stack (v2.8)
Section titled “Mental model: the layer stack (v2.8)”Since #459 the system prompt assembles from ordered layers, stable → volatile (see the layer table for the full reference):
agent.CoreInstruction(built-in, always present). The harness contract: tool-dispatch rules (the runtime serializes state-mutating tools — #460 — so no prompt-level edit-sequencing rule is needed) and the post-boundary framing (“when prior conversation arrives wrapped in[Conversation compacted...]or[The prior task is complete...], read it as authoritative shared history; don’t re-run tools”).- Provider quirks (built-in, model-selected). Measured workarounds — Gemini gets the parallel-batching mandate; Claude gets none.
- Mode overlay (built-in). Interactive (default) or autonomous disposition, via
agent.WithMode. - Your memory — user-global
~/.core-agent/AGENTS.md, then projectAGENTS.md, in that order. Since v2.8 memory sits AFTER the built-in layers: your instructions win on conflict with the built-in communication defaults, and editing memory never invalidates the prompt-cached core. - Operator/consumer appends —
agent.append_system_promptconfig,--append-system-prompt, oragent.WithExtraInstruction.
The model sees all active layers concatenated on every turn. core-agent doesn’t let you skip the core short of a full replace — it’s load-bearing for a half-dozen specific failure modes (post-summary recap behavior; edit races). Layer your own guidance in 4–5, where it takes precedence naturally.
If you’re building a library binary and need to replace everything, see Library API → agent.WithInstruction — and note what you give up.
What goes in AGENTS.md
Section titled “What goes in AGENTS.md”Goes in: role framing, voice, style preferences, cross-cutting do/don’t lists, project-specific constraints, fact references the agent needs every turn.
Does NOT go in: multi-step procedures, large reference content, per-task playbooks. Those go in skills.
Heuristic: if the content applies to every turn, it’s AGENTS.md. If it applies to specific tasks, it’s a skill. “Always wrap errors with fmt.Errorf” is AGENTS.md. “Here’s how to run our deploy checklist” is a skill.
Don’t write the generic half — include it (v2.10)
Section titled “Don’t write the generic half — include it (v2.10)”Most of a good AGENTS.md isn’t about your project. “Read before you write”, “don’t call it fixed without reading it back”, “say what you did not do” — every operator writes some version of these, badly, from memory. Since #656 core-agent ships them, and you include them by name:
# Logistics platform on-call
You answer questions about one GKE cluster for the team that runs it.
@include builtin:sre
## Your environment — use these exact values
- Cluster context: `logistics-prod-use4`- You reach the cluster only by running `kubectl` through `bash`.- `kubectl` is authenticated with **read verbs only**. A mutating verb returns a `forbidden` error from the API server; it is not a prompt you can approve past.Three personas ship in the binary:
| Name | What it carries |
|---|---|
builtin:core | Identity and honesty — report as established only what a tool call in this session returned; your tool list is the truth about what you can do; repetition is not progress. Included automatically by the other two. |
builtin:coder | Conduct for changing software in a codebase you did not write. |
builtin:sre | Conduct for operating a live system you did not build. |
The split the personas are built on is identity → equipment → conduct. Identity and conduct are portable, so they ship with the binary. Equipment — which tools, which cluster, which project, which approval mode — is not, so it stays in your AGENTS.md, next to the config that makes it true. That’s the whole shape of the example above: twelve lines of equipment and one @include.
Mechanics:
-
@include builtin:coderand@include builtin:srein the same file emitbuiltin:coreonce — the loader dedupes by name, and duplicated system-prompt text is how a prompt stops being believed. -
A typo is fatal.
@include builtin:sre-agentfails the load and the daemon refuses to start, rather than running a quietly weaker agent. -
Builtins resolve out of the binary, so they need nothing on disk. An
AGENTS.mdprojected into a read-only ConfigMap can include one. -
They are not interpolated —
${env:...}in a builtin would silently resolve to empty, so there is none. -
They show up in the startup summary by name, alongside your own files:
core-agent: instruction: loaded 3 file(s): /w/.agents/AGENTS.md, builtin:sre, builtin:core
For the full text of each persona, the incident behind every rule, and how to add one, see docs/persona-library.md.
Patterns that work
Section titled “Patterns that work”Lead with role, then the do/don’t list
Section titled “Lead with role, then the do/don’t list”You are a Go code-reviewer for the Acme platform monorepo.[role]
## What you review- Staged diffs- Test failures- Build failures[scope]
## House style- Errors wrapped with fmt.Errorf("op: %w", err)- Table-driven tests with t.Parallel()[rules]
## What NOT to do- Don't propose opportunistic refactors- Don't run go test ./... without asking — slow in this repo[anti-rules]Models follow imperative direction much better than aspirational descriptions. “You are a thoughtful, helpful assistant” doesn’t change behavior; “Always ask before running tests” does.
Be specific. Concrete > general
Section titled “Be specific. Concrete > general”Bad:
Write good error handling.
Good:
Wrap errors with
fmt.Errorf("op: %w", err)— noterrors.New. Bareerrors.Newonly for sentinel errors at package scope.
The first version is unfalsifiable; the model has no way to tell whether its output complies. The second has a concrete pattern the model can check against.
Include examples for patterns you care about
Section titled “Include examples for patterns you care about”Two lines of code showing your error-wrapping convention is worth a paragraph describing it:
## Logging
Use slog with structured fields, not log.Printf string interpolation:
```go// goodslog.Info("user authenticated", "user_id", uid, "method", "oauth")
// badlog.Printf("user %d authenticated via %s", uid, "oauth")The agent's outputs will mirror the patterns you show.
### Don't fight the built-in layers
The core + quirks + overlay layers already tell the model to:- Batch independent tool calls in parallel (on Gemini, where it's measured to matter)- Treat post-boundary summaries as authoritative shared history- Narrate before non-trivial work and report outcomes plainly
You don't need to repeat any of that. If you want to *amplify* one of these (e.g., "when batching tool calls, prefer read_many_files over multiple parallel read_file calls"), do it as an addition — but don't restate the baseline. Re-instruction crowds out your project-specific content.
### Name the failure modes you want the model to avoid
The most effective `AGENTS.md` rules name a specific failure pattern:
> Never use `panic` in library code; never call `os.Exit` outside of `main`. These bypass normal error propagation and make the binary unkillable from the agent's perspective.
The "and here's why" half makes the rule more durable — the model generalizes better when it understands the constraint, vs. memorizing a list of forbidden tokens.
---
## Patterns specific to autonomous use
The unattended agent needs an `AGENTS.md` that's *more* explicit than an interactive one. With no operator course-correcting in the loop, ambiguity becomes either over-eager action or paralysis.
### Crisp success criterion
Tell the agent what "done" looks like:
> Use `report_done` when ALL of the following are true: (1) every Pod is `Running`, (2) `kubectl rollout status` returned 0, (3) the SLO error budget hasn't decreased in the last 10 minutes. Put the evidence for each in the result — the values you actually read, not the fact that you checked.
Without an explicit completion signal, you're relying on budget caps alone to stop the run — the model will happily keep going indefinitely against a vague goal.
Say what the result must **contain**, never what genre of document it is. "A one-paragraph summary" reads as an instruction to write *about* the work, and you get "monitored the deployment and everything looks healthy" — a sentence that forces whoever reads it to go and check for themselves. Naming the content obligation ("the values you actually read") gets you the values. This is the same rule core-agent's own tool descriptions follow; see `AGENTS.md` under Conventions.
### Explicit don't-do list
A monitor that decides to scale a deployment because "that would fix it" is exactly the failure mode unattended runs are notorious for:
> ## What NOT to do>> - Don't propose remediations. You're a watcher, not an actor.> - Don't run `kubectl describe` repeatedly on the same resource — it floods the audit log. Use `get -o json` and parse.> - Don't post status updates when everything is healthy. Silence = good.
Be aggressive about the don't-do list for autonomous agents. The cost of an unintended action with no operator review is much higher than the cost of being slightly too restrictive.
### Bounded tool surface
If the agent has access to a tool it shouldn't use, name that explicitly:
> The `bash` tool is available but should be used ONLY for the `kubectl get` / `kubectl rollout status` / `kubectl logs` commands listed above. Any other shell invocation is a bug — use a more specific tool or surface a `report_alert` describing what you'd need.
You can also remove the tool entirely with `tools.disable`; the `AGENTS.md` framing is belt-and-suspenders for when removal is overkill.
---
## Model-specific quirks
### Frontier models (Gemini 3 Pro, Claude Opus 4.7)
Generally follow nuanced instruction. Tolerate some ambiguity. The big risk is *over-thoroughness* — they'll verify digests by re-reading the source, repeat tool calls "to be sure," explore tangents. Mitigate with explicit "don't verify; trust the digest" rules where the cost matters.
### Flash/Haiku tier (Gemini 2.5 Flash, Claude Haiku)
Instruction-following is weaker. The model may understand the rule but not connect it to the current decision. Mitigations:
- **Lead with the rule.** "STOP. Before any other action, read X." (Yes, in caps, at the start of the section. Flash needs the attention grab.)- **Repeat critical rules.** What feels redundant to a human is load-bearing for Flash.- **Name the failure mode the rule prevents.** "Without this you will accidentally run kubectl apply" — Flash generalizes better when it understands the consequence.- **Test on the actual model.** Rules that work on Pro may not work on Flash. The post-checkpoint loop issue we caught in v2.0 was exactly this — Pro behaved perfectly, Flash needed stronger framing.
### Smaller / older models (Gemini 2.5, older Claude)
Behavior varies. Test what you're shipping; don't assume cross-model portability. The patterns above are starting points, not guarantees.
---
## Case study: the post-checkpoint loop
A real iteration cycle from v2.0 development, showing how `AGENTS.md` + boundary framing + tool descriptions all interact.
**Symptom (smoke 2026-05-27, Gemini Flash as parent):** after `/done` wrote a checkpoint, the next turn ran `list_dir` infinitely instead of reading from the checkpoint summary.
**Root cause analysis:**1. The `compactingService` was correctly slicing pre-checkpoint history out of the LLM request — verified by smaller input-token counts on the next turn.2. The checkpoint summary was being passed through as a `RoleUser` message wrapped with framing — verified by inspecting the request.3. The framing said "[Conversation compacted. Below is the handover summary...]" — but Flash interpreted the summary's leading `# Task complete` heading as "everything's done, fresh start" and ignored the summary content.
**Fix (PR #54):**- New `checkpointPrefix` distinct from compaction's: "[The prior task is complete... the conversation CONTINUES from here; the next user message is part of the SAME ongoing session, not a fresh start. When the user asks anything about the prior task — what was done, what files were touched, what was learned, recap, summary, status — read FROM the record below..."- Renamed `# Task complete` → `# Task` in the summarizer prompt. Removed terminal language.- Added a sentence to the built-in instruction (today's `agent.CoreInstruction`) about both framing shapes (`[Conversation compacted…]` and `[The prior task is complete…]`).
**Lesson for your `AGENTS.md`:**
If you're using Flash as parent and observe similar "model ignores context" patterns, the answer is rarely "the model is broken." It's almost always that:
1. Your `AGENTS.md` rule is too soft (aspirational, not imperative)2. Your rule competes with another signal in the context (a section heading, a prior tool result, the default instruction)3. The rule needs to lead with attention-grabbing language for Flash to register it
The fix is usually 1-2 sentences, not a rewrite. But the iteration loop — observe the failure, identify which signal won, strengthen the losing signal — applies whether you're tuning `core-agent`'s own framings or your own project's `AGENTS.md`.
---
## How to iterate on an `AGENTS.md`
**Start minimal.** A 20-line `AGENTS.md` with role + scope + 3-4 rules covers 80% of agents. Don't try to anticipate every failure mode up front; you'll write rules for things that never happen and miss the things that do.
**Add rules as failures surface.** Use `core-agent` for a few real tasks. When the agent does something wrong, ask yourself:
- Is it a one-off, or will it happen again? If one-off, ignore.- If recurring: is the existing instruction too vague, or is it missing entirely?- Write the new rule as a do/don't statement naming the specific behavior you want.
**Don't over-prompt.** A 500-line `AGENTS.md` is harder for the model to follow than a 100-line one. Long instructions get truncated in the model's attention; rules late in the document carry less weight. If your `AGENTS.md` is growing past ~150 lines, consider:
- Moving stable, task-specific procedures to skills- Moving cross-cutting reference content (lists of services, library inventories) to a skill's `references/` directory the agent can fetch on demand- Splitting per-team rules into team-specific skills with descriptions like "When asked about billing code"
**Version it.** `AGENTS.md` is in your repo, so it's already version-controlled. When you change a rule, leave a brief comment explaining the change. Six months from now you'll thank yourself.
---
## Anti-patterns
| Pattern | Why it fails | Fix ||---|---|---|| "You are a thoughtful, helpful assistant" | Aspirational; doesn't change behavior | Specific role: "You are a Go code-reviewer for X" || "Be careful with error handling" | Unfalsifiable | Concrete rule: "Wrap errors with `fmt.Errorf(\"op: %w\", err)`" || Repeating the default instruction | Crowds out project-specific content | Only mention defaults to amplify them, not restate || 30 do/don't rules, no role framing | Model has nothing to ground them in | Lead with what the agent IS, then layer rules on top || "Always do X" rules with no exception list | Edge cases force the model to silently violate | "Always X EXCEPT when Y" — name the carve-outs || Markdown subsection per individual rule | Visual noise, hard to scan | Group related rules under a single heading || Examples written in prose, not code | Models mirror what they see; show, don't tell | Use ``` fenced code blocks for examples || Soft language ("you might consider", "it would be nice if") | Models discount soft instructions | Imperative voice: "Do X", "Never Y" |
---
## Where to go next
- **[Skills](/agent-design/skills/)** — when to write a skill vs. an `AGENTS.md` rule- **[Subagents and wrappers](/agent-design/subagents-and-wrappers/)** — getting the model to use `agentic_*` tools + spawn background subagents- **[Cost efficiency](/agent-design/cost-efficiency/)** — model selection, the Pro+Flash split, `/context` observability- **[Interactive workflows](/run/interactive/workflows/)** — worked examples with full `AGENTS.md` files- **[Reference → Skills](/concepts/skills/)** — `SKILL.md` schema details