Teach an open-source agent your codebase: AGENTS.md and goose, step by step
Write a short AGENTS.md for a real repository, install goose on Windows, macOS or Linux, give it a recipe you can validate and an MCP tool, and let your tests, not the agent, decide when a task is done.
Every session, your coding agent arrives as a stranger. It doesn't know that your checks run with npm run check, that money in your app is whole kobo, or that nobody pushes from a laptop. So it guesses, you spend ten minutes correcting it, and tomorrow it guesses again. This post fixes that with two open tools and one small repository you can copy.
The idea in plain words
A coding agent is a program that uses a language model to read code, run commands and edit files until a task is finished. AGENTS.md is a plain Markdown file at the root of a repository that tells any such agent how the project works: the commands, the conventions and the things it must never do. Its official site calls it a README for agents and reports use in more than 60,000 open-source projects.
goose is an open-source agent that runs on your own machine, as a desktop app or a command-line tool, with the model provider you choose. It reads AGENTS.md by default, runs repeatable tasks written as YAML recipes, and gains tools through MCP, the Model Context Protocol: a standard way to plug a tool server into any agent. AGENTS.md, goose and MCP are all projects of the Agentic AI Foundation, part of the Linux Foundation, so none of them is tied to one vendor.
This matters beyond convenience. We are heading towards agents that act, pay and are trusted to do both, and that trust won't come from a clever model. It comes from instructions the agent can't miss and checks it can't argue with. I made the general case in The harness is the product. Here we build it.
Before you start
- Node.js 20.19 or newer, because ESLint 10 needs it. I tested on Node 24.18.0 with npm 11.16.0.
- Git and a terminal: bash or zsh on macOS and Linux, PowerShell or Git Bash on Windows.
- uv for the MCP server in step 7. I used uv 0.12.3.
- A model for goose: an API key from a provider such as Anthropic, OpenAI, Google Gemini, Groq or OpenRouter, or a local model through Ollama. goose's provider page notes that Gemini and Groq offer free tiers.
Exact versions used: goose 1.53.0, eslint 10.12.0, @eslint/js 10.0.1, mcp-server-fetch 2026.8.18 and @modelcontextprotocol/inspector 2.9.0.
Build it, step by step
Step 1: make a tiny repository
The sample app, kobo-split, splits a bill between friends to the kobo, so the shares always add up. It is small on purpose. The point is the files around the code.
# bash: macOS, Linux, Git Bash
mkdir -p kobo-split/src kobo-split/bin kobo-split/test kobo-split/.goose/recipes
cd kobo-split
git init
# PowerShell
New-Item -ItemType Directory -Force kobo-split/src, kobo-split/bin, kobo-split/test, kobo-split/.goose/recipes
Set-Location kobo-split
git init
Create these files exactly. Dependencies are pinned to exact versions, which matters more when an agent runs npm install for you.
package.json
{
"name": "kobo-split",
"version": "0.1.0",
"private": true,
"type": "module",
"bin": { "kobo-split": "bin/kobo-split.js" },
"scripts": {
"test": "node --test",
"lint": "eslint .",
"check": "eslint . && node --test --test-reporter=dot"
},
"engines": { "node": ">=20.19" },
"devDependencies": {
"@eslint/js": "10.0.1",
"eslint": "10.12.0"
}
}
eslint.config.js
// eslint.config.js
import js from '@eslint/js';
export default [
js.configs.recommended,
{
languageOptions: {
ecmaVersion: 2024,
sourceType: 'module',
globals: { console: 'readonly', process: 'readonly' },
},
},
];
src/split.js
// src/split.js
// Split a bill fairly. All money is integer kobo (100 kobo = 1 naira).
export function splitBill(totalKobo, people) {
if (!Number.isInteger(totalKobo) || totalKobo < 0) {
throw new RangeError('totalKobo must be a non-negative integer');
}
if (!Array.isArray(people) || people.length === 0) {
throw new RangeError('people must be a non-empty array of names');
}
const base = Math.floor(totalKobo / people.length);
let remainder = totalKobo - base * people.length;
// The first `remainder` people pay one extra kobo, so the shares always add up.
return people.map((name) => {
const extra = remainder > 0 ? 1 : 0;
remainder -= extra;
return { name, kobo: base + extra };
});
}
bin/kobo-split.js
#!/usr/bin/env node
// bin/kobo-split.js
// Usage: node bin/kobo-split.js <total-in-kobo> <name> [name...]
import { splitBill } from '../src/split.js';
const [total, ...people] = process.argv.slice(2);
try {
for (const share of splitBill(Number(total), people)) {
console.log(`${share.name}: ${share.kobo} kobo`);
}
} catch (err) {
console.error(`error: ${err.message}`);
process.exit(1);
}
test/split.test.js
// test/split.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { splitBill } from '../src/split.js';
test('splits evenly when it divides', () => {
assert.deepEqual(splitBill(300000, ['Ada', 'Bayo', 'Chidi']), [
{ name: 'Ada', kobo: 100000 },
{ name: 'Bayo', kobo: 100000 },
{ name: 'Chidi', kobo: 100000 },
]);
});
test('shares always add up to the total', () => {
const shares = splitBill(100001, ['Ada', 'Bayo', 'Chidi']);
assert.equal(shares.reduce((sum, s) => sum + s.kobo, 0), 100001);
assert.deepEqual(shares.map((s) => s.kobo), [33334, 33334, 33333]);
});
test('rejects floats and empty groups', () => {
assert.throws(() => splitBill(10.5, ['Ada']), RangeError);
assert.throws(() => splitBill(100, []), RangeError);
});
test/rules.test.js
// test/rules.test.js
// House rules from AGENTS.md, enforced as a test so an agent cannot talk its way past them.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { readdirSync, readFileSync } from 'node:fs';
const banned = ['parseFloat(', '.toFixed('];
test('money stays in integer kobo inside src/', () => {
for (const file of readdirSync('src')) {
const code = readFileSync(`src/${file}`, 'utf8');
for (const word of banned) {
assert.ok(
!code.includes(word),
`src/${file} uses ${word} Money is integer kobo: use Math.floor and % 100, never floats. See AGENTS.md.`,
);
}
}
});
That last test matters most. It turns a house rule into a check: if any file in src/ uses parseFloat( or .toFixed(, the build fails with a message that tells the agent how to fix it.
.gitignore
node_modules/
.env
README.md
# kobo-split
Split a bill between friends, to the kobo, so the shares always add up.
## Use it
node bin/kobo-split.js 100001 Ada Bayo Chidi
Ada: 33334 kobo
Bayo: 33334 kobo
Chidi: 33333 kobo
## Develop
npm install
npm test
npm install
npm run check
Step 2: write AGENTS.md
AGENTS.md
# AGENTS.md
kobo-split: a tiny Node.js CLI that splits a bill between people, in kobo.
## Commands
- Install: `npm ci`
- Test: `npm test` (Node's built-in runner). One file: `node --test test/split.test.js`
- Lint: `npm run lint` (ESLint 10, flat config in `eslint.config.js`)
- Check everything: `npm run check` (lint, then tests, about 2 seconds).
Run it before you say a task is done. It must exit 0.
## Layout
- `src/`: pure functions, no I/O. `bin/` is the only place that reads argv or prints.
- `test/`: one `*.test.js` per module in `src/`, using `node:test` and `node:assert/strict`.
- `test/rules.test.js` enforces the house rules below. Read its failure message; it says how to fix the problem.
## Conventions
- ES modules only. Node 20.19 or newer.
- Money is always an integer number of kobo. Never use floats for money; convert to naira only when printing.
- Shares must always add up to the total. Any change to splitting logic needs a test that checks the sum.
- Throw `RangeError` for bad input. Do not return null or NaN.
## Never
- Never add a runtime dependency without asking. Dev dependencies are pinned to exact versions.
- Never edit or delete a test to make it pass. Fix the code, or stop and explain.
- Never read or print `.env`.
- Never run `git push` or `npm publish`.
It's 27 lines. Compare it with the README above: same project, different reader.
| README.md | AGENTS.md | |
|---|---|---|
| Reader | People: users and new contributors | Coding agents |
| Job | What it is, how to use it | Exact commands, rules, limits |
| Style | Friendly, explains why | Short, imperative, checkable |
| When it's read | Once, when someone arrives | At the start of every session, so every line costs tokens |
| Typical line | "Split a bill between friends" | "Run npm run check before you say a task is done" |
Three habits shape the file. Every command was run, as written, in a fresh clone. Every convention is one the code doesn't make obvious. And the "Never" list names actions, not attitudes: "be careful with money" is a wish, while "never use floats for money" can be tested, and test/rules.test.js tests it. Commit the baseline so you can read the agent's diff later:
git add .
git commit -m "kobo-split with AGENTS.md"
Step 3: install goose
These are the official commands from goose's install page. Setting GOOSE_VERSION pins the release, and CONFIGURE=false skips the set-up wizard so you can run it in step 4.
# macOS, with Homebrew
brew install block-goose-cli
# macOS, Linux, WSL or Git Bash on Windows
curl -fsSL https://github.com/aaif-goose/goose/releases/download/stable/download_cli.sh | GOOSE_VERSION=v1.53.0 CONFIGURE=false bash
# Windows PowerShell (x86_64; the script stops on Windows ARM64)
Invoke-WebRequest -Uri "https://raw.githubusercontent.com/aaif-goose/goose/main/download_cli.ps1" -OutFile "download_cli.ps1"
$env:GOOSE_VERSION = "v1.53.0"
$env:CONFIGURE = "false"
.\download_cli.ps1
$env:PATH += ";$env:USERPROFILE\.local\bin"
If PowerShell refuses to run the script, run powershell -ExecutionPolicy Bypass -File .\download_cli.ps1, which relaxes the policy for that one process only. Both scripts install to ~/.local/bin (on Windows, %USERPROFILE%\.local\bin), so add that folder to your PATH for good. Then check:
goose --version
Step 4: connect a model
goose configure
Choose Configure Providers, pick your provider, paste the key when asked and choose a model. goose keeps the key in your system keyring where one is available, never in config.yaml. You can also set GOOSE_PROVIDER and GOOSE_MODEL as environment variables, with the provider's own key variable such as ANTHROPIC_API_KEY, OPENAI_API_KEY or GOOGLE_API_KEY. Run goose info to see where the config lives: ~/.config/goose/config.yaml on macOS and Linux, and %APPDATA%\Block\goose\config\config.yaml on Windows in 1.53.0.
Keep keys out of the repository. Anything in the working folder, a .env included, is something the agent can read.
Step 5: a session that reads AGENTS.md
goose's default mode lets it edit files and run commands without asking. For a first run, switch to smart approval, which approves low-risk actions and asks before risky ones.
# bash
export GOOSE_MODE=smart_approve
goose session --name naira
# PowerShell
$env:GOOSE_MODE = "smart_approve"
goose session --name naira
Type this, and notice that it doesn't mention AGENTS.md. That's the test.
Add a formatNaira(kobo) function in a new file src/naira.js that returns
strings like ₦1,500,000.05, with tests in test/naira.test.js, and use it
for the CLI output.
Watch for three things. Does it run npm run check before claiming success, without being told? Does it avoid .toFixed(), or, if it reaches for it, does the rules test send it back? And does git diff --stat show three files: the new module, its test and the CLI? Type /exit to leave.
src/naira.js (a hand-written answer that passes)
// src/naira.js
// Format integer kobo as naira for display, without floats.
export function formatNaira(kobo) {
if (!Number.isInteger(kobo) || kobo < 0) {
throw new RangeError('kobo must be a non-negative integer');
}
const naira = Math.floor(kobo / 100).toLocaleString('en-NG');
const rest = String(kobo % 100).padStart(2, '0');
return `₦${naira}.${rest}`;
}
test/naira.test.js
// test/naira.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { formatNaira } from '../src/naira.js';
test('formats kobo as naira with thousands separators', () => {
assert.equal(formatNaira(33334), '₦333.34');
assert.equal(formatNaira(150000005), '₦1,500,000.05');
assert.equal(formatNaira(0), '₦0.00');
});
test('rejects floats', () => {
assert.throws(() => formatNaira(10.5), RangeError);
});
bin/kobo-split.js (updated)
#!/usr/bin/env node
// bin/kobo-split.js
// Usage: node bin/kobo-split.js <total-in-kobo> <name> [name...]
import { splitBill } from '../src/split.js';
import { formatNaira } from '../src/naira.js';
const [total, ...people] = process.argv.slice(2);
try {
for (const share of splitBill(Number(total), people)) {
console.log(`${share.name}: ${formatNaira(share.kobo)}`);
}
} catch (err) {
console.error(`error: ${err.message}`);
process.exit(1);
}
Step 6: a recipe for the task you repeat
A recipe is a YAML file that packages instructions, a prompt, parameters, extensions and checks, so a task runs the same way every time. Saved in .goose/recipes/, it travels with the repository and goose can find it by name.
.goose/recipes/add-tested-change.yaml
# .goose/recipes/add-tested-change.yaml
version: "1.0.0"
title: "Add a tested change"
description: "Make one small change to a module in src/, with tests, and stop only when lint and tests pass."
instructions: |
You are working in a small Node.js repository. Read AGENTS.md first and follow it.
Work only in {{ module }} and its test file in test/.
Write or update a failing test first, then change the code until it passes.
Run `npm run check` before you finish. If it fails, fix the code, not the test.
Never add a runtime dependency. Never edit .env. Never run git push.
prompt: |
In {{ module }}: {{ change }}
When `npm run check` exits 0, reply with a short summary of the files you changed.
parameters:
- key: module
input_type: string
requirement: required
description: "Path of the module to change, for example src/split.js"
- key: change
input_type: string
requirement: required
description: "The change you want, in one or two sentences"
extensions:
- type: builtin
name: developer
display_name: Developer
timeout: 300
bundled: true
settings:
max_turns: 25
retry:
max_retries: 2
checks:
- type: shell
command: "npm run check"
Each part has a job. instructions set the agent's standing orders and prompt is the task, which a headless run needs. Required parameters can't have defaults, and goose rejects a parameter the templates never use. The retry block is the back-pressure: after the agent finishes, goose runs npm run check itself, and if it fails it resets the conversation and tries again, up to twice. The model's opinion of its own work doesn't count.
goose recipe validate .goose/recipes/add-tested-change.yaml
goose run --recipe add-tested-change --explain
goose run --recipe add-tested-change --params module=src/split.js --params "change=Reject duplicate names with a RangeError"
The first two run without a model. The third does the work.
Step 7: add an MCP extension
goose's built-in Developer extension already gives it shell and file tools. Let's add the official MCP fetch server, so goose can read a documentation page when it needs one. First, check the server starts and see what it offers, without goose or a model:
npx -y @modelcontextprotocol/inspector@2.9.0 --cli uvx mcp-server-fetch==2026.8.18 --method tools/list
Then add it in one of three ways. Interactively: goose configure, then Add Extension, Command-line Extension, name it fetch, give the command uvx mcp-server-fetch==2026.8.18 and a timeout of 300. Or edit the config file from step 4:
extensions:
fetch:
enabled: true
type: stdio
name: fetch
cmd: uvx
args: ["mcp-server-fetch==2026.8.18"]
envs: {}
timeout: 300
Or for one session only:
goose session --with-extension "uvx mcp-server-fetch==2026.8.18"
To give it only to the recipe, add the same server under extensions: there, as type: stdio with cmd, args and timeout. That version also passed goose recipe validate.
Run it and see it work
The baseline checks, in the repository root. The dots are four passing tests; the dot reporter keeps success quiet.
$ npm run check
> kobo-split@0.1.0 check
> eslint . && node --test --test-reporter=dot
....
$ node bin/kobo-split.js 100001 Ada Bayo Chidi
Ada: 33334 kobo
Bayo: 33334 kobo
Chidi: 33333 kobo
With the hand-written naira change in place, the check shows six dots and the CLI prints naira:
$ node bin/kobo-split.js 100001 Ada Bayo Chidi
Ada: ₦333.34
Bayo: ₦333.34
Chidi: ₦333.33
goose, the recipe and the MCP server (long paths shortened to …):
$ goose --version
1.53.0
$ goose recipe validate .goose/recipes/add-tested-change.yaml
✓ recipe file is valid
$ goose recipe list
Available recipes:
add-tested-change - Make one small change to a module in src/, with tests, and stop only when lint and tests pass. - local: \\?\C:\…\kobo-split\.goose\recipes\add-tested-change.yaml
$ goose run --recipe add-tested-change --explain
🔍 Loading recipe: Add a tested change
📄 Description:
Make one small change to a module in src/, with tests, and stop only when lint and tests pass.
⚙️ Recipe Parameters:
- module (string, required): Path of the module to change, for example src/split.js
- change (string, required): The change you want, in one or two sentences
📥 Parameters used to load this recipe:
recipe_dir (built-in): \\?\C:\…\kobo-split\.goose\recipes
🔴 Missing parameters in the command line if you want to run the recipe:
- module
- change
📩 Please provide the following parameters in the command line if you want to run the recipe::
--params module=your_value --params change=your_value
$ npx -y @modelcontextprotocol/inspector@2.9.0 --cli uvx mcp-server-fetch==2026.8.18 --method tools/list | grep '"name"'
"name": "fetch",
Now break things on purpose, because a check you've never seen fail is a check you can't trust. A recipe whose prompt no longer uses {{ change }}:
$ goose recipe validate bad-recipe.yaml
Error: ✗ recipe file is invalid:
Unnecessary parameter definitions: change.
And a src/bad.js that formats money with (kobo / 100).toFixed(2) (stack trace trimmed):
$ npm run check
X...
Failed tests:
✖ money stays in integer kobo inside src/ (2.7813ms)
AssertionError [ERR_ASSERTION]: src/bad.js uses .toFixed( Money is integer kobo: use Math.floor and % 100, never floats. See AGENTS.md.
Without a provider, goose run stops with error: No provider configured. Run 'goose configure' first., which is how I know where my verification ends.
Going deeper
AGENTS.md, skills, MCP tools or a test?
Each layer costs context differently, so put each piece of knowledge where it is cheapest and most reliable.
| Put it in | When | What it costs | Example |
|---|---|---|---|
| AGENTS.md | True for every task in this repo | In context every request | The check command, the kobo rule |
| A skill | A procedure needed sometimes | A name and description until used | How to cut a release |
| An MCP tool | An action the shell and files can't do well | Tool descriptions while enabled | Fetch docs, query a database |
| A recipe | A task you repeat with different inputs | Only when you run it | Add a tested change |
| A test or lint rule | A rule that must hold | Nothing until it fails | No floats in src/ |
goose finds skills, each a folder with a SKILL.md, in .agents/skills/ in the project and ~/.agents/skills/ globally. At the start of a session it lists only their names and descriptions, and loads the full text when a task needs it. The last row is the one people skip: anything that must hold belongs in a check, with AGENTS.md pointing to it.
Nested AGENTS.md in a monorepo
shop/
├── AGENTS.md # repo-wide: install, CI, never push
├── apps/pos/AGENTS.md # how to run the till app on a phone
└── packages/payments/AGENTS.md # kobo rules, idempotent webhooks
The AGENTS.md convention is that the file closest to the edited code wins, and an explicit instruction in chat beats every file. goose loads context files from your working folder up to the repository root at the start, then picks up nested ones as it reads or edits files in those folders. Once loaded they stay for the session, so restart after you edit one. goose reads both AGENTS.md and .goosehints by default; set CONTEXT_FILE_NAMES='["AGENTS.md"]' if you want one file to serve every agent your team uses.
Keep it short
goose's docs say hints go into the system prompt for every request, so length costs money and attention on every turn. Mine is 27 lines and I'd resist going past 60. Add a line only after you've seen an agent make the mistake it prevents. Delete a line once a linter or test enforces it. Point to docs/ instead of pasting it in. The research on why longer files often hurt is in The harness is the product.
Feedback loops make agents reliable
Agents are confident, and confidence isn't evidence. Reliability comes from back-pressure: automatic checks that push back on bad work before a person has to. This repository has four layers. The rules test turns a convention into a failure, with a message that says how to fix it. The dot reporter keeps success to one line, so passing output doesn't fill the context. The recipe's retry check runs outside the model, so "done" means exit code 0. And --max-turns and --max-tool-repetitions on goose run stop a stuck agent from going round in circles.
One trade-off to know: a retry resets the conversation but not the files, so the next attempt starts from whatever the last one left. An on_failure command such as git stash --include-untracked gives each try a clean tree, at the cost of throwing that work away. Run recipes on a branch either way.
Permissions and sandboxing
| Mode | Value | Behaviour |
|---|---|---|
| Completely autonomous (default) | auto | Edits, runs and deletes without asking |
| Smart approval | smart_approve | Approves low-risk actions, asks for the rest |
| Manual approval | approve | Asks before every tool call |
| Chat only | chat | No tools, no file changes |
Set a mode with GOOSE_MODE, in goose configure, or with /mode inside a session. In the approval modes, per-tool permissions (Always Allow, Ask Before, Never Allow) live under goose configure, then goose settings, then Tool Permission.
The bigger fact: goose runs with your user's permissions and is not sandboxed at the operating-system level. An experimental macOS sandbox shipped in 1.25.0 and has since been removed. So isolation is your job. Run extensions inside a container with --container, or use the Container Use MCP server that goose's docs walk through. Keep real keys out of the environment the agent can see. Adversary mode (rules in ~/.config/goose/adversary.md) has a second reviewer check shell calls before they run, but it fails open, and goose calls its prompt-injection detection a safeguard, not a guarantee. The fetch server shows why: its own tool description tells the model it now has internet access. Every page it fetches is untrusted text in your agent's context. A line in AGENTS.md saying "never read .env" is a request. Keeping secrets off the machine is a control.
Your 30-minute build
Write an AGENTS.md and one goose recipe for your own project. Tick these before you call it done:
- AGENTS.md is under 40 lines, and every command in it ran as written in a fresh clone.
- At least one "Never" or convention line is backed by a test or lint rule that fails when broken, with a message that says how to fix it.
goose recipe validateprints✓ recipe file is valid, and the recipe'sretrycheck runs your check command.- A fresh goose session, asked for a small change without being told about AGENTS.md, runs your check command before it says it's finished.
Three ideas if you need a project:
- Market price book. A trader's price list in kobo with bulk discounts. Rule: a discount never takes a price below cost. Recipe: add a product category with tests.
- Danfo fare table. Fares between bus stops on one route. Rule: a fare is never negative and a longer trip never costs less. Recipe: add a stop and update the fare tests.
- School fees instalments. Split a term's fees into instalments that add up to the kobo. Rule: every instalment is a whole number and the total always matches. Recipe: add a new payment plan, such as weekly.
Hand this to your coding agent
I want to add an AGENTS.md and one goose recipe to this repository.
Work with me step by step, and do not add runtime dependencies.
Stack: whatever this repository already uses. Files you will create or
change: AGENTS.md at the root, one test or lint rule, and
.goose/recipes/<task-name>.yaml.
1. Read the package files, scripts, test set-up, lint config and CI config.
List the exact install, test, lint and "check everything" commands.
If there is no single check command, propose one that runs lint and
then tests and exits non-zero on failure, and add it.
2. Clone the repository into ../agents-md-check and run each command there.
Show me the output. If a command fails, tell me; do not hide it.
3. Ask me for the 3 to 5 rules newcomers most often get wrong here, and
the actions an agent must never take.
4. Write AGENTS.md, under 40 lines, with the sections Commands, Layout,
Conventions and Never. Include only commands you have run.
5. Pick one rule that code can check and add a test or lint rule that
fails when it is broken. Its failure message must say how to fix it.
6. Write .goose/recipes/<task-name>.yaml for one task we repeat, with
required parameters, instructions that say to read AGENTS.md first,
and a retry check that runs the check command.
7. Run: goose recipe validate .goose/recipes/<task-name>.yaml
Acceptance checks, all must pass:
- AGENTS.md is under 40 lines and every command in it ran in the clone.
- The new rule fails on a deliberate violation and passes once reverted.
- goose recipe validate prints "✓ recipe file is valid".
- The check command exits 0 on the final state.
Test before you finish: run the check command and the recipe validation
one last time and paste both outputs. Never edit or delete an existing
test to make it pass.Learn it properly
This post is a hands-on slice of Track 5 of the AI Study Group, Harness engineering: AGENTS.md, skills, back-pressure, permissions and sandboxes, ending with a lab where you build a small harness that won't accept "done" until the tests pass. It's free and works with local models.
Sources
- AGENTS.md: the format, nested files and the precedence rule
- Agentic AI Foundation projects: MCP, goose and AGENTS.md under the Linux Foundation
- goose has a new home: the move to aaif-goose/goose and goose-docs.ai
- Install goose and the v1.53.0 release
- Configure LLM provider and configuration files
- Environment variables:
GOOSE_MODE,CONTEXT_FILE_NAMES,GOOSE_PROVIDER - Providing hints to goose: AGENTS.md loading and nested files
- Recipe reference and storing recipes
- Using extensions and the Developer extension
- Using skills
- Permission modes and tool permissions
- goose v1.25.0 release notes: the removed macOS sandbox
- Adversary mode, prompt injection detection and isolated development environments
- CLI commands:
/modeand session flags - MCP fetch server and the MCP Inspector
- Node.js test runner and ESLint configuration files
- Versions used: goose 1.53.0, Node.js 24.18.0, npm 11.16.0, eslint 10.12.0, @eslint/js 10.0.1, mcp-server-fetch 2026.8.18, @modelcontextprotocol/inspector 2.9.0, uv 0.12.3