Overview
AI coding agents can be surprisingly effective at helping you build, calibrate, analyse and extend mizer models. Unlike a web search or a static manual, an agent can read your actual data and scripts, suggest specific parameter changes, write R snippets for your situation, and explain what functions do in plain language.
This article explains how to get an agent working alongside your R session and shows a range of example tasks where one can save you significant time.
The examples say “RStudio” where they need to name something, but very little here depends on it. The agent runs in a terminal and reads files in your project directory, and it reaches your R session over a socket, so the setup works the same from RStudio, Positron, a bare R console, ESS or the VS Code R extension. The one thing that does need RStudio or Positron is the agent’s ability to read the document you have open in the editor; everything else — the reference card, the skills, the documentation lookups, your global environment, running mizer code and seeing the plots — works in any R session.
Quick start: one-line setup
mizerAgents bundles everything an AI agent needs to understand the mizer API. You only need to run this once in your R project:
pak::pak("sizespectrum/mizerAgents")
mizerAgents::setup_mizer_agent()This creates the following files in your working directory:
-
MIZER-AGENTS.md— a concise mizer reference: core workflow, key object descriptions, a species parameter table, and the path to the bundled API index. This file is package-managed and can be updated when mizer is upgraded. -
AGENTS.md,CLAUDE.mdandGEMINI.md— your project-specific instruction files. Each is created with a short package-managed block at the top that points agents atMIZER-AGENTS.md; the rest of each file is yours. All three carry the same block, because none of them is a fallback for another: Claude Code readsCLAUDE.mdand notAGENTS.md, and Gemini CLI readsGEMINI.md, so an agent that looks only for its own named file should still find the mizer context. The block is the only thing added to these files: no@AGENTS.mdimport is written or removed, so which of your own instructions reach which agent stays exactly as you had it. -
MCP configuration for a server named
r-mizerthat connects the agent to your R session, described in the next section. Every agent uses a different file for this, so all of them are written:.mcp.json,.codex/config.toml,.gemini/settings.json,.agents/mcp_config.json,.cursor/mcp.json,.vscode/mcp.jsonand.posit/assistant/settings.json.
MIZER-AGENTS.md is always updated when you run
setup_mizer_agent(). In the three instruction files only
the block between the <!-- mizerAgents: start --> and
<!-- mizerAgents: end --> comments is
package-managed: it is refreshed each time you run
setup_mizer_agent(), so improvements to it reach existing
projects, while everything you write outside those markers is preserved.
If a CLAUDE.md or GEMINI.md of yours already
contains @AGENTS.md, it is left untouched, since the block
reaches the agent through that import.
Connecting the agent to your R session
mizerAgents bundles an index of the mizer API — every
exported function with a one-line description, grouped by workflow stage
— but deliberately not the argument lists. A snapshot of those would
drift from the mizer you have installed, and it would drift
quietly: a call written against last year’s signature usually
still runs and returns plausible numbers, which is the hardest kind of
error to notice in a model.
So the index tells the agent which function it needs, and the installed package tells it how to call that function. The btw package supplies the second half: an MCP server that lets the agent read the documentation of your installed packages, along with your R session. Install it once:
install.packages("btw")setup_mizer_agent() has already registered that server
under the name r-mizer. Agents have not converged on a
single place to configure MCP servers, nor even on a single schema, so
it writes all of them:
| Agent | File |
|---|---|
| Claude Code | .mcp.json |
| Codex CLI | .codex/config.toml |
| Gemini CLI | .gemini/settings.json |
| Antigravity CLI | .agents/mcp_config.json |
| Cursor | .cursor/mcp.json |
| VS Code / Copilot | .vscode/mcp.json |
| Posit Assistant | .posit/assistant/settings.json |
The files are small, they sit in different places, and each agent
ignores the others’, so the effect is that whichever agent you or a
collaborator reaches for, the connection is already there. Commit them
along with AGENTS.md. If you would rather have fewer files,
name the ones you use:
mizerAgents::setup_mizer_agent(agents = c("claude", "posit"))"posit" is worth knowing about if you would rather not
leave the IDE at all: Posit
Assistant runs inside RStudio as well as Positron, and connects to
the same R session this whole mechanism is built around.
GitHub Copilot CLI is the exception: it reads MCP servers only from
the user-wide ~/.copilot/mcp-config.json, which is not a
file a project setup function should be editing, so
setup_mizer_agent() prints the snippet to paste there
instead.
The server runs as its own R process, so it does not see your session until you hand it over. In your R console — RStudio’s, or whichever one you work in — run:
mizerAgents::connect_mizer_agent()This wraps btw::btw_mcp_session(), which does the work,
and adds a report of which agents are configured to reach the session
you have just handed over — and a warning if none are, since handing
over a session that nothing is set up to reach otherwise looks exactly
like success.
The agent can now read help pages, vignettes and release notes for
your installed mizer, list the objects in your global environment, and —
in RStudio or Positron — read the document you have open in the editor.
That means it can check the argument names of
newMultispeciesParams() or setBevertonHolt()
instead of recalling them, which is the single most common source of
mizer code that looks right and is not.
To have every new session connect itself, run
mizerAgents::setup_mizer_agent(rprofile = TRUE)which adds the btw_mcp_session() call to your project’s
.Rprofile. Your sessions then connect themselves, and you
no longer call connect_mizer_agent() at all. R reads that
file only when it starts in the project directory: RStudio and Positron
guarantee that when you open the project, but if you start R from a
shell you have to be in the project root, or else keep calling
connect_mizer_agent().
Which R session does the agent use?
Connected sessions are not private to a project. They all go into one list belonging to your user account, and every agent you run can see all of them, so an agent has to pick. It picks the connected session whose working directory is the directory the agent was started in. Keep one connected session per project, start the agent from that project’s directory, and the rest of this section will never come up.
Here is what happens when it does.
Two projects open at the same time. Each R session connects itself, each agent is started from its own project directory, and each agent gets its own session. Directory matching is exactly the case this handles, so nothing special is needed.
An agent started where nothing is connected. If just
one session is connected anywhere on your machine, the agent will use it
— even though it belongs to a different project. If none is, or if
several are, the agent’s R tools quietly run in a scratch R process of
their own instead: it will report an empty global environment, and
objects it creates never appear in yours. Neither case produces an error
message, so the symptom to watch for is an agent that cannot see the
objects you are looking at. Run connect_mizer_agent() in
the right session, then restart the agent — an agent holds on to the
session it first reached for as long as it runs.
Two R sessions open on the same project. Now the
directory does not identify a session, and the agent picks neither. Ask
it to run list_r_sessions — it will show something like
2: celtic-sea (RStudio) — and then to select the one you
want with select_r_session. Every agent has these two
tools, and the choice sticks until the agent stops. The parenthesis is
the command that started R, so RStudio and Positron sessions are named
as such and one started from a shell shows the path to the R executable
instead. That is a label, not a requirement: any of them can be
connected and selected.
Not sure which session an agent is in. Ask it to run
Sys.getpid() and compare the number with
Sys.getpid() in your console. If they differ, it is working
somewhere else.
Do not connect the same session twice. A second call
does not refresh the connection; it breaks it. The session stops
answering agents and stops appearing in list_r_sessions,
and only restarting R puts it right. connect_mizer_agent()
checks for this and tells you the session is already connected rather
than doing it, so the way to hit it is to call
btw::btw_mcp_session() yourself in a session that is
already connected — in particular in a project set up with
rprofile = TRUE, where every session connects itself at
startup.
Letting the agent run mizer code
The agent can also evaluate R code in your session, and does so by
default. It can call project() or steady() on
your params object and plot the result, and the plot comes
back to it as an image — so it can look at a spectrum or a biomass
trajectory and judge whether the model is behaving, rather than guessing
from numbers. This is the mode in which an agent is most useful for
calibration work.
The trade-off is real: that code runs in your global environment with
no sandboxing, so a careless assignment can overwrite an object you
spent an hour building. MIZER-AGENTS.md tells the agent to
assign to new names and to announce overwrites, but the safeguard you
should rely on is version control. Commit your work before turning the
agent loose on it. If you would rather have a read-only connection —
documentation and inspection, but no execution — use
mizerAgents::setup_mizer_agent(run_r = FALSE)and setup_mizer_agent(r_session = FALSE) skips the MCP
setup altogether.
If your project is a mizer extension package
When you are writing an extension package rather than a model, add btw’s package development tools:
mizerAgents::setup_mizer_agent(pkg_dev = TRUE)The agent can then run load_all(),
document(), test(), check() and
test coverage in your session instead of shelling out to
devtools in a terminal. The gain is not just tidiness:
after load_all() your new code is live in the same session,
so the agent can immediately build a MizerParams object and
try the rate function it has just written. Combined with the
extend-mizer skill, this makes the write–load–test loop
something the agent can close on its own. These tools are off by
default, as they do nothing useful in an ordinary modelling project.
Running an AI agent in the terminal
The agent itself is a command-line program. All it needs is a shell
whose working directory is your project root: that is where it finds
AGENTS.md, MIZER-AGENTS.md, the skills and the
MCP configuration, and it is the directory the MCP server matches
against your connected R session.
Any terminal will do. In RStudio, the Terminal tab
(Tools → Terminal → New Terminal) already opens in the
project directory; Positron and VS Code have the same thing. Or open a
terminal window of your own, cd to the project, and start
the agent there — worth doing if you work across multiple monitors, and
the normal arrangement if you do not use an IDE with a terminal pane at
all. Either way the agent reads and edits the same files your R session
sees.
If you work in Emacs with ESS, in vim, in a plain R console, or over
SSH on a server, nothing changes: run setup_mizer_agent()
once in the project, connect_mizer_agent() in each R
session, and start the agent from a shell in the project directory. The
only capability you give up is the agent reading the file you currently
have open, which needs the rstudioapi package and so works
only in RStudio and Positron. Tell it which file you mean instead.
What an AI agent can help with
-
Building a model — translating a species parameter
spreadsheet into a working
newMultispeciesParams()call, identifying missing columns and suggesting sensible defaults. -
Calibration — diagnosing why a species is not
converging in
steady(), or suggesting which parameters to adjust when biomasses are off. - Writing analysis code — producing R code to compare scenarios, extract rates, or reshape output for plotting.
-
Explaining functions — describing what a function
does and when to use it, especially for less familiar parts of the API
such as
setRateFunction()orsetComponent(). - Extending mizer — scaffolding a custom rate function or a new ecosystem component.
- Debugging — reading an error message or a suspicious plot alongside your code and suggesting fixes.
An agent is not infallible. Always verify R code it produces actually runs and gives sensible results, especially for anything involving numerical tuning.
Giving the agent context about your project
After running setup_mizer_agent() you will have an
AGENTS.md in your project directory. It begins with a short
block, marked off by <!-- mizerAgents: start --> and
<!-- mizerAgents: end --> comments, that shims in the
mizer package reference. Open AGENTS.md and add a short
description of your own project below the closing marker — that
part of the file is yours and is never overwritten, whereas anything you
change inside the markers is replaced the next time you run
setup_mizer_agent(). For example:
<!-- mizerAgents: start — managed by setup_mizer_agent(), edits are overwritten -->
This project uses [mizer](https://sizespectrum.org/mizer/) for size-spectrum
modelling. Read `MIZER-AGENTS.md` before writing or changing mizer code — it is
imported below if your tool resolves `@` imports. It is generated by
`mizerAgents::setup_mizer_agent()`; don't edit it by hand.
@MIZER-AGENTS.md
<!-- mizerAgents: end -->
# Celtic Sea mizer model
This project builds and analyses a mizer model for the Celtic Sea,
calibrated to ICES survey data for 2010–2020.
## Species
Cod, Haddock, Whiting, Herring, Sprat, Mackerel — 6 species.
## Key files
- `species_params.csv` — species parameter table
- `celtic_model.R` — builds and saves the MizerParams object
- `scenarios.R` — runs and compares fishing scenarios
## Workflow
1. Build: `celtic_model.R`, saves `celtic_params.rds`
2. Load: `params <- readRDS("celtic_params.rds")`
3. Project and analyse in `scenarios.R`This project-specific context costs ten minutes to write and saves re-explaining your setup at the start of every session.
What the agent works out for itself
Most of what an agent learns about your model, it learns while
working: that this model only settles with a smaller
steady() tolerance, that one species’ beta is
doing something odd, that a scenario has to be run a particular way. It
will want to write that down, and it writes where it was reading — in
the skill it was following, under .claude/skills/. Those
skill files are installed by setup_mizer_agent() and
refreshed when you run it again, so that is the wrong place for it.
Each skill therefore has a NOTES.md beside it that this
package never writes. The skills tell agents to read it alongside the
SKILL.md, to treat it as taking precedence where the two
disagree, and to record project-specific findings there:
.claude/skills/calibrate-model/SKILL.md # ours, refreshed
.claude/skills/calibrate-model/NOTES.md # yours, never touched
Commit those files. They are as much a part of the project as the
code, and a collaborator’s agent picks them up too. If you or your agent
do edit a SKILL.md directly, the edit is not lost either:
setup_mizer_agent() notices, keeps your version, and writes
the new one beside it as SKILL.md.new for you to merge.
Some of what an agent discovers is not about your project at all — a mizer function that behaves differently from how the skill describes it, a workflow that would work anywhere. That belongs upstream, in an issue on the mizerAgents repository, so that the next release carries it to every project instead of leaving it in yours. The skills tell agents to offer this, so you may find yourself asked.
Example prompts
The examples below illustrate the kinds of request that work well. Type them directly into the agent’s terminal session, or into a browser-based AI assistant.
Getting a model started
“I have a CSV of species parameters for the Celtic Sea at
data/celtic_species_params.csv. Read it and help me set up a mizer model. Tell me which required columns are missing and what sensible defaults I could use.”
The agent will inspect the file, identify missing columns such as
w_max or beta, suggest defaults, and produce a
newMultispeciesParams() call ready to run.
Diagnosing calibration problems
“I’ve run
steady(params)and biomass of Whiting is about 4× too high compared to thebiomass_observedcolumn. What’s the most likely cause and what should I try first?”
A good response will walk through the relevant parameters
(gamma, beta, sigma,
z0) and suggest targeted changes rather than blind
adjustments.
Writing scenario code
“Write R code that runs three fishing scenarios — status quo, 50% effort reduction, and effort doubled — for 50 years each, then plots yield through time for all three on the same axes with a legend.”
The agent will produce a self-contained snippet using
project() and plotYield(). Check it, run it,
and ask for tweaks.
Understanding a function
“What does
setBevertonHolt()actually do? I’m confused about thereproduction_levelargument and when I should change it during calibration.”
You will get a plain-language explanation of the Beverton-Holt
stock–recruitment relationship in mizer, what
reproduction_level represents ecologically, and guidance on
when to adjust it.
Adding custom biology
“I want growth rates to scale with water temperature following a Q10 relationship. Show me how to use
setRateFunction()to replace the encounter rate so thatparams@other_params$tempis used.”
The agent will produce a custom encounter function and the
setRateFunction() call to register it — the kind of task
that would otherwise require careful reading of the extending mizer article.
Debugging an error
“Running
project(params, t_max = 50)throws this warning repeatedly: [paste warning text]. What does it mean and how do I fix it?”
Share the exact text of the warning. The agent will usually identify the cause (e.g. a species going near-extinct, a numerical instability from too large a time step) and suggest a remedy.
Tips for effective use
Be specific. “My model doesn’t work” is hard to help
with. “Cod biomass is 10× too high after steady() and I’ve
already tried reducing gamma” is much easier.
Share actual files. CLI agents can read your
.rds files, CSVs and R scripts. Give them your real data
and they produce targeted suggestions rather than generic advice.
Iterate. If the first response misses the mark, say so and explain what you expected. Agents improve substantially with clarifying feedback within a session.
Verify numerical results. An agent knows the mizer API well but may not know whether a particular parameter value is ecologically plausible for your system. Apply your domain knowledge.
Teach the agent After you have had to explain something to the agent to make the agent’s answers more useful to you, tell the agent to “Add what I just told you to AGENTS.md so that you know this in future sessions.”
Keep sessions focused. One task per session tends to produce better results than a long chain of unrelated questions.