Agent Harnesses & Configuration
A model on its own only produces text — the harness is what lets it read files, run commands, and remember. This is how you configure one: the real files behind instructions, skills, tools, commands, hooks, memory, plugins, and scheduling, using Claude Code as the worked example.
What are Agent Harnesses?
The framework that wraps a model to create controllable, extensible agent systems.
The Core Concept
An agent harness wraps a language model and turns it from something that predicts text into something that gets work done — reading and changing files, running commands, remembering what happened, spawning helpers, asking you for a decision, scheduling its own follow-up.
The model brings the judgment. The harness brings everything else:
- Tools — the actions it can actually take
- Configuration — instructions, conventions, and limits
- Context — what it knows at the start of each turn
- Control — approval gates and hard blocks on risky actions
Popular Agent Harnesses
Claude Code
Anthropic's CLI agent harness with first-class support for skills, slash commands, lifecycle hooks, and the Model Context Protocol (MCP). Designed for software engineering workflows with git integration, code review, and file operations. Config lives in .claude/ with layered inheritance (global → project → local).
Codex
OpenAI's coding agent available through ChatGPT, the Codex CLI, desktop apps, and IDE integrations. Runs on GPT-5.3-Codex models optimized for software engineering with reinforcement learning on real coding tasks. Executes work in sandboxed cloud containers with optional local execution modes.
Cursor
AI-powered IDE built as a fork of VS Code with integrated agent capabilities. Features AI-powered code generation, intelligent autocompletion, codebase understanding, and agent mode for planning and building features. Includes support for MCP servers, custom rules, and skills.
Beyond Coding Agents
The three above are agents you run. These are libraries you build agents with — same underlying problems, different audience:
- LangChain / LangGraph — composable chains and stateful graphs, Python-first
- LlamaIndex — indexing and retrieval over your own data
- CrewAI — multi-agent teams with assigned roles
- AutoGPT — early autonomous goal loops
They all solve the same four problems — tools, memory, orchestration, safety — and differ mainly on who is expected to do the building.
Why Use a Harness?
A model API on its own can only return text. Ask it about the weather and it tells you it has no way to check.
The harness closes that loop. It offers the model a set of tools, runs whichever one the model picks, feeds the result back, and repeats until the work is done. That loop is the whole trick.
What you get for it:
- Repeatability — the same instructions and limits apply every session, even though the model's exact wording never repeats
- Safety — you decide what it may touch, and the harness enforces that rather than asking the model to behave
- Extensibility — new capabilities are files and servers you add, not model retraining
- Observability — every action is a tool call you can log and inspect
Related Concepts
External Resources
Configuration Structure
The actual files and folders Claude Code reads — what each one is for, and which belong to you versus the team.
Configuration Is Just Files
The examples below use Claude Code. Other harnesses differ in file names and formats — Codex and most other agents read AGENTS.md, Cursor uses .cursor/rules/ — but the shape is the same everywhere: plain text files in the repo, one natural-language instructions file, one structured settings file, and a global copy of each that applies to every project.
There is no settings UI and no database. Everything is readable, diffable, and checkable into git.
~/.claude/ # you, in every project
CLAUDE.md # your standing instructions
settings.json # model, permissions, hooks
commands/ # your slash commands
skills/ # your skills
my-project/
CLAUDE.md # project instructions (checked in)
.mcp.json # MCP servers for this project
.claude/
settings.json # team settings (checked in)
settings.local.json # your overrides (gitignored)
rules/ # instructions scoped to file paths
commands/ # project slash commands
skills/ # project skills
agents/ # subagent definitions
CLAUDE.md — Instructions in Plain English
The file you will edit most. There is no schema: you write sentences, and they load into context at the start of every session.
Run `npm test` before committing.
Prefer server components; add "use client" only when you need state.
API handlers live in `src/api/handlers/`.
Each of those is concrete enough to check, which is the test for a good line. "Run npm test before committing" lands where "Test your changes" does not.
The harder question is what to leave out. One rule settles most cases: if Claude can read it off the codebase, it is noise. Directory trees, dependency lists, and architecture overviews get rediscovered on demand and cost context every session. What cannot be rediscovered is the reasoning — the pitfall that bit you, the convention that differs from the tool's default.
| Belongs | Leave out |
|---|---|
| Build and test commands | Directory listings |
| Conventions that differ from the default | Dependency lists |
| Pitfalls, and why they exist | Architecture overviews |
| "Always do X" rules | Multi-step procedures — those are a skill |
Add a line when Claude makes the same mistake twice, or when you type a correction you already typed last session. /doctor reviews a checked-in file and proposes trims along exactly this split.
Two features keep it from sprawling:
@imports pull in another file. Starting your CLAUDE.md with@AGENTS.mdlets Claude and other coding agents share one set of instructions instead of duplicating them..claude/rules/scopes instructions to paths. A rule markedpaths: ["src/api/**"]only loads when Claude touches the API.
Keep it under about 200 lines. It loads every session, so it costs context, and long files get followed less closely. Run /init to generate a first draft from the codebase.
settings.json — Exact Values, Not Sentences
A fixed list of keys. Where CLAUDE.md takes any sentence you write, settings.json only accepts keys the harness defines — invent one and it is ignored.
{
"model": "claude-sonnet-5",
"outputStyle": "Explanatory",
"permissions": {
"allow": ["Bash(npm test)", "Bash(git status)"],
"deny": ["Bash(rm -rf *)"]
}
}
Those keys do three different jobs:
- Values —
modelsets a mode. The setting is the whole answer. - Inline rules —
permissions,hooks, andmcpServerscarry their full definition right there in the JSON. - Names —
outputStyleandenabledPluginsname something stored elsewhere; the harness finds the file by that name, not by a path you give it.
CLAUDE.md is guidance the model reads and can misread. Permissions are enforced by the harness before a tool runs. If something must be impossible rather than discouraged, it belongs here or in a hook — never in prose.
outputStyle — The One Key That Edits the System Prompt
Most settings configure the harness. outputStyle changes the prompt Claude itself receives.
---
name: Diagrams first
description: Lead every explanation with a diagram
keep-coding-instructions: true
---
When explaining architecture or data flow, start with a diagram, then explain in prose.
Save it under ~/.claude/output-styles/ or .claude/output-styles/, then select it by name — "outputStyle": "Diagrams first" in settings, or /config. The file sitting on disk does nothing until something names it.
keep-coding-instructions: truekeeps the built-in engineering behavior. Leave it off to replace it entirely, which suits a non-coding assistant.- The system prompt is assembled once at session start, so a change lands only after
/clearor a new session. - It applies to the main conversation only. Subagents run their own system prompt, so they ignore it.
This is why a style is followed more closely than CLAUDE.md. CLAUDE.md arrives as a user message after the system prompt — Claude reads it as something you said. A style is part of the instructions Claude starts with.
Everything Else Is a Folder
commands/, skills/, agents/, rules/ — drop a markdown file in and it is available next session. No registration step and no manifest to update.
Nothing in settings.json points at these directories. The harness finds them by location, which is why the two halves of your config stay separate: known keys in the JSON, everything else discovered by where it sits.
Which File Wins?
The same setting can appear in all three scopes at once. Config Layering covers the precedence rules.
Related Concepts
External Resources
Skills
Packaging a repeatable procedure as a folder Claude loads only when it's needed.
What Is a Skill?
A skill is a folder containing a SKILL.md file: frontmatter describing when to use it, then markdown instructions for what to do. Claude loads it on its own when relevant, or you invoke it with /skill-name.
| Scope | Path |
|---|---|
| Personal | ~/.claude/skills/<name>/SKILL.md |
| Project | .claude/skills/<name>/SKILL.md |
| Plugin | <plugin>/skills/<name>/SKILL.md |
A Real Skill
---
description: Summarizes uncommitted changes and flags anything risky. Use when the user asks what changed or wants a commit message.
---
## Current changes
!`git diff HEAD`
## Instructions
Summarize the changes above in two or three bullets, then list any risks:
missing error handling, hardcoded values, tests that need updating.
The ! line runs that command and drops its output in place before Claude ever sees the file, so the instructions arrive with the real diff already in them. The docs call this dynamic context injection.
Why Not Just Put It in CLAUDE.md?
Because CLAUDE.md is read at the start of every session, so every line takes up room in the context window whether you need it that day or not. A skill's instructions are only read when the skill runs. A 300-line checklist you use twice a month takes up nothing on the other days.
The dividing line: facts that are always true go in CLAUDE.md, procedures you run sometimes become skills.
Frontmatter Worth Knowing
| Field | What it does |
|---|---|
description | How Claude decides to load it — put the use case first |
disable-model-invocation | true means only you can trigger it, with /name |
allowed-tools | Tools pre-approved for the turn the skill runs in |
paths | Auto-load only when working on matching files |
context: fork | Run in a subagent instead of the main conversation |
Supporting Files
Because a skill is a directory, it can carry reference docs, scripts, and templates next to SKILL.md. Claude reads them on demand, so a large API reference costs nothing until the skill actually needs it.
Skills follow the Agent Skills open standard, so the same folder works across several tools.
Related Concepts
External Resources
Tools
The actions an agent can take, and the permission rules that decide which ones it may.
Every Action Goes Through a Tool
Reading a file, running a command, searching the web — each is a named tool the harness implements and the model asks for by name. Nothing reaches your machine any other way, which is what makes the list below worth knowing.
| Tool | Does |
|---|---|
Read Write Edit | read and change files |
Glob Grep | find files, search contents |
Bash | run shell commands |
WebFetch WebSearch | reach the internet |
Task | spawn a subagent |
Tool vs Skill
A tool is a single capability the harness implements in code. A skill is a markdown file telling Claude how to combine tools to get something done.
You can't write a new tool in markdown — new tools arrive through MCP servers. You write skills constantly.
How You Limit What It Can Do
A tool call is the moment something actually happens: a file gets overwritten, a command runs, a request leaves your machine. So before any tool runs, the harness checks it against rules you wrote. The model does not get to approve its own actions. Those rules live in settings.json:
{
"permissions": {
"allow": ["Bash(npm run test:*)", "Read(./src/**)"],
"ask": ["Bash(git push *)"],
"deny": ["Bash(rm -rf *)", "Read(./.env)"]
}
}
The syntax is Tool(specifier). Bash(npm *) matches any command starting with npm, Read(./.env) matches one file, WebFetch(domain:example.com) matches a host.
Two rules that trip people up:
- Deny beats ask beats allow. A broad
denywins over a narrowerallow, so a deny rule can never carry exceptions. - A bare tool name is not the same as a scoped one. Denying
Bashremoves the tool from Claude's context entirely, so it never sees it. DenyingBash(rm *)leaves the tool available and blocks matching calls when Claude tries them.
Adding Tools with MCP
An MCP server contributes tools that appear namespaced, like mcp__github__get_issue. They obey the same permission syntax as built-ins, so nothing about the control model changes when you add them.
Related Concepts
External Resources
Subagents
Handing a scoped job to a second agent with its own context window, so the main conversation stays clean.
What Is a Subagent?
A subagent is a second agent the main one spawns to handle a scoped piece of work. It gets its own context window and its own system prompt, does the job, and hands back only a summary.
The point is context. A search that reads forty files would crowd out your conversation. Run it in a subagent and just the answer comes back.
Where They Come From
| Source | How you get one |
|---|---|
| Built in | Explore for search, Plan for design |
| Project | a markdown file in .claude/agents/ |
| Skill | context: fork in the frontmatter |
A definition sets the model, the tools it may use, and the prompt it runs under:
---
name: test-writer
description: Writes tests for changed code. Use after a feature lands.
tools: Read, Grep, Edit, Bash
model: sonnet
---
Write tests covering the changed behavior. Match the framework already used
in this repo. Never modify source files, only test files.
The narrow tools list is doing real work: a subagent without Write cannot wander outside the job you gave it.
What They Don't Inherit
A subagent starts cold. It cannot see your conversation, and it runs its own system prompt — so an output style you set never reaches it. A fork is the exception, because it inherits the parent's full context.
That isolation cuts both ways. The main conversation stays short, but the subagent knows nothing you have not written into its definition or its prompt.
Related Concepts
External Resources
Commands
Turning carefully written instructions into a slash command you can invoke by name.
Slash Commands Are Skills Now
Custom commands and skills have merged. .claude/commands/deploy.md and .claude/skills/deploy/SKILL.md both create /deploy and behave the same way. Existing commands/ files keep working; if both exist, the skill directory wins.
Use a single file in commands/ for a one-shot prompt. Use a skill directory when you want supporting files, or want Claude to invoke it on its own.
You Type a Little, Claude Reads a Lot
A command is shorthand. You type a few characters, Claude receives the full instructions:
You type: /review
Claude gets: Review the changed files on this branch. Check for
correctness bugs, missing tests, and anything that
breaks existing callers. Report findings as file:line.
That is the whole idea. The value isn't the shortcut — it's that the instructions were written once, deliberately, instead of retyped approximately every time.
Arguments and Live Context
---
description: Open a pull request for the current branch
argument-hint: [base-branch]
---
Base branch: $1
!`git log --oneline main..HEAD`
Open a PR against the base branch above, summarizing those commits.
$1 and $2 take positional arguments, $ARGUMENTS takes everything you typed, and argument-hint shows up in autocomplete. The ! line runs a command and inlines its output before Claude sees the file.
Built-ins
/init, /context, /memory, /compact, and /plugin ship with the harness. Your own commands appear in the same / menu alongside them.
External Resources
Hooks
Running your own code at fixed points in the lifecycle, independent of what Claude decides.
Hooks Run Your Code, Not Claude's
A hook is a shell command the harness runs at a fixed point in the lifecycle. It fires whether or not Claude thinks it should — which is the entire reason to use one.
CLAUDE.md saying "run the formatter after editing" is a request. A hook runs the formatter after editing.
Where They Live
In settings.json, nested event → matcher → handler:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/format.sh"
}
]
}
]
}
}
The Events You'll Actually Use
| Event | Fires |
|---|---|
PreToolUse | before a tool runs — can block it |
PostToolUse | after a tool succeeds |
UserPromptSubmit | when you send a message |
SessionStart | at session start, useful for injecting context |
Stop | when Claude finishes a turn |
There are many more, covering subagents, compaction, file changes, and notifications.
Blocking a Tool Call
A PreToolUse hook receives the pending call as JSON on stdin and answers with its exit code:
#!/bin/bash
if [[ "$(jq -r '.tool_input.command')" == rm* ]]; then
echo "Blocked: rm is not allowed in this repo" >&2
exit 2
fi
exit 0
Exit 2 blocks the call and hands stderr back to Claude as the reason it failed. Exit 0 stays out of the way and lets the normal permission flow continue.
When to Reach for One
Use a hook when something must happen every single time: formatting, linting, audit logging, blocking a dangerous command. Use CLAUDE.md when you want judgment applied. Hooks have no judgment, which is exactly the point.
External Resources
Memory
What carries across sessions, what fills the context window, and what survives compaction.
Two Kinds of Memory
Every session starts with an empty context window. Two mechanisms carry knowledge across the gap:
| CLAUDE.md | Auto memory | |
|---|---|---|
| Written by | you | Claude |
| Holds | instructions and rules | what it learned while working |
| Loaded | every session, in full | every session, index only |
Auto Memory
Claude takes notes for itself as it works — build commands, debugging insights, preferences it picked up from your corrections. They live outside the repo, per project:
~/.claude/projects/<project>/memory/ MEMORY.md # index, loaded every session debugging.md # topic file, read on demand
Only the first 200 lines of MEMORY.md load at startup. Topic files are read when needed, which is what keeps the index cheap enough to load every time.
Run /memory to browse and edit what it saved. It's plain markdown, and you can delete anything that's wrong.
The Context Window Is the Real Constraint
Instructions, files read, tool output, and conversation all compete for the same window. When it fills, older turns are summarized and replaced by that summary.
What survives compaction: your project CLAUDE.md, which is re-read from disk and re-injected. What doesn't: anything you only said in chat.
That's the practical rule for builders. If it needs to outlive this conversation, it goes in a file.
Checking What Actually Loaded
/context shows what's in the window right now, including which memory files loaded. When Claude ignores an instruction you know you wrote, look there first — usually the file never loaded at all.
Related Concepts
External Resources
Plugins
Installing bundled skills, commands, hooks, and MCP servers as one package, and connecting external tools via MCP.
What Is a Plugin?
A plugin bundles skills, commands, hooks, and MCP servers into one installable package, so a whole capability — not just one tool — gets added at once.
Installing a Plugin
> /plugin marketplace add anthropics/claude-code-plugins > /plugin install pr-review-toolkit@claude-code-plugins
Once installed, its pieces just show up — no wiring step. Its skills appear under a plugin-scoped name (pr-review-toolkit:code-reviewer), its commands become slash commands, and any MCP servers it ships are ready to call.
What a Plugin Actually Is
On disk, a plugin is a directory with a manifest pointing at its pieces:
// .claude-plugin/plugin.json
{
"name": "pr-review-toolkit",
"version": "1.0.0",
"skills": "./skills",
"commands": "./commands",
"mcpServers": "./.mcp.json"
}
Model Context Protocol (MCP)
MCP is the standard way an agent connects to an external tool or data source — a plugin can ship one, or you can add one yourself, without writing a client.
// .mcp.json
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_TOKEN": "${GITHUB_TOKEN}" }
}
}
}
Or add one from the CLI instead of hand-editing the file:
claude mcp add github -- npx -y @modelcontextprotocol/server-github
Either way, its tools just appear — named like mcp__github__create_issue — with no client code to write or discovery step to run manually.
Related Concepts
External Resources
Verification
Giving the agent a signal it cannot talk its way past, and gating the end of a turn on it.
"Done" Is a Claim, Not Evidence
An agent will tell you it finished. That sentence is generated text like any other — it is what a finished turn usually sounds like, not a measurement of your repo.
A verification loop replaces the claim with output the agent has to react to. Tests, a type checker, and a build all produce the same two things: an exit code, and an error message specific enough to act on.
Naming the Command
The cheapest version is one line in CLAUDE.md:
Run `npm test` and `npm run typecheck` before calling a task done.
That is guidance. Claude usually follows it and sometimes does not, which is the entire reason the next part exists.
Gating the Turn With a Hook
A Stop hook runs when Claude finishes responding. Exit 2 prevents it from stopping and hands your stderr back as context:
#!/bin/bash
if ! npm test --silent; then
echo "Tests are failing. Fix them before finishing." >&2
exit 2
fi
exit 0
Claude reads that message and keeps working instead of ending the turn. The same exit code on PostToolUse cannot block anything — the tool already ran — but it still surfaces stderr, which is what you want for a linter that reports and moves on. See Hooks for where these go in settings.json.
Order the Gates by Speed
Every gate runs on every turn, so its cost repeats:
| Gate | Cost | Where it belongs |
|---|---|---|
| formatter | instant | PostToolUse, on edits |
| type check | seconds | Stop |
| unit tests | seconds to minutes | Stop |
| full suite, end-to-end | minutes | CI, and let the agent read the failure |
A gate that adds two minutes to every turn gets switched off within a day. Keep the fast checks local and push the slow ones out to CI.
Related Concepts
External Resources
Worked examples of running formatters and checks automatically
Scheduling
Running agent work on schedules, loops, and event triggers — set up conversationally, not with config files or scripts.
Why Schedule Agent Work?
Agents can run autonomously:
- Monitoring - Check systems on a cadence
- Reporting - Generate daily/weekly summaries
- Maintenance - Keep a PR healthy until it merges
- Alerting - Detect problems and notify
Cloud Routines: /schedule
Describe the job in plain language; the harness turns it into a scheduled routine — no schedule file to hand-write:
> /schedule check PR #482's CI every 10 minutes and message me when it's green Created routine "pr-482-ci-watch" schedule: every 10 minutes prompt: "Check CI status on PR #482. If green, message the user it's ready to merge."
Listing it later shows the same thing a cron table would, generated from that conversation:
> /schedule list pr-482-ci-watch */10 * * * * next run in 4m
Interval Loops: /loop
For keeping one session alive on a cadence rather than spinning up a separate cloud routine:
/loop 5m /babysit-prs
This reruns /babysit-prs every 5 minutes in the current session. Omit the interval and the agent self-paces instead — waking on real events (CI finishing, a deploy failing) rather than a fixed clock.
Event-Driven Follow-Through
The common trigger isn't a webhook handler — it's a standing instruction that fires after an action, written once in project config:
After opening a pull request, automatically start /loop /babysit-prs for that PR.
Opening the PR is the event; starting the loop is the response. Nothing to deploy or maintain separately — the instruction lives alongside the rest of the agent's configuration.
External Resources
Choosing a Primitive
A decision table for picking the right mechanism, and the one rule separating guidance from enforcement.
Start From the Question
Each primitive in this pack answers a different question. The common mistake is reaching for a flexible one when the job needed a strict one.
| You want | Reach for | Because |
|---|---|---|
| Claude to always know a fact about the project | CLAUDE.md | loads every session |
| A procedure you run only sometimes | a skill | loads just when it is used |
| The same long prompt, typed short | a slash command | a skill you invoke by name |
| Something to happen every single time | a hook | runs your code, not Claude's judgment |
| An action to be impossible | a permission deny rule | enforced before the tool runs |
| A big search that doesn't flood the chat | a subagent | separate context window |
| A capability the harness doesn't have | an MCP server | adds genuinely new tools |
| Several of these shipped together | a plugin | one install |
| Work to happen while you're away | a schedule or a loop | runs without you |
The One Rule Worth Remembering
Instructions are interpreted. Permissions and hooks are enforced.
CLAUDE.md and skills shape what Claude is likely to do. Permission rules and hooks decide what is possible at all, and the model gets no vote.
So the question is not "how do I tell it to stop?" but what happens when the instruction does not land. If the worst case is that you repeat yourself, write it down. If the worst case is a deleted file, a force push, or a secret sent somewhere, make it impossible.
Related Concepts
External Resources
Config Layering
Layering global, shared-project, and personal-project configuration so nothing steps on the team's setup.
Configuration Hierarchy
Highest priority wins. The top two rungs are the ones people forget.
Managed policy org-wide, cannot be overridden
↓
Command line --model, --settings
↓
Project Local .claude/settings.local.json (gitignored)
↓
Project .claude/settings.json (checked in)
↓
Global ~/.claude/settings.json
Precedence is resolved per key, not per file: a project that sets only model doesn't discard your global permissions.
Most settings reload as you change them. outputStyle is the exception — it is part of the system prompt, which is built once at session start, so it takes effect on /clear or the next session.
Global (~/.claude/)
Personal preferences that apply to every project — default model, your own instructions, commands and skills you use everywhere.
// ~/.claude/settings.json
{
"model": "claude-sonnet-5",
"permissions": {
"allow": ["Bash(git *)"]
}
}
Project (.claude/, checked into git)
Shared conventions everyone on the project should follow — instructions, permissions, commands the whole team relies on.
.claude/ CLAUDE.md # project instructions settings.json # shared permissions/config commands/ # shared slash commands
Project Local (.claude/settings.local.json, gitignored)
Personal overrides for this one project that aren't shared with the team — your own extra permissions, experiments, machine-specific tweaks.
// .claude/settings.local.json (gitignored)
{
"permissions": {
"allow": ["Bash(npm run dev:*)"]
}
}
Project local overrides project, which overrides global.
Related Concepts
External Resources
Standard for organizing user config files on Unix systems