●AGENTS — When you create a Managed Agents session, pass agent_with_overrides to swap the model, system prompt, tools, MCP servers, or skills for that single session●KEYS — API keys in the Claude Console can now carry an expiration (a preset, a custom duration, or Never), with an email reminder before keys that live 7 days or longer expire●MCP — Admins can provision MCP connectors org-wide through their identity provider, starting with Okta, so users get connector access automatically on first login●WORKBENCH — The legacy Workbench at platform.claude.com/workbench retires on August 17, along with the experimental prompt generate, improve, and templatize APIs●REVIEW — Claude Code adds a background /code-review so you can keep working while a review runs, with better MCP and Windows path handling alongside●FASTEND — Fast mode for Claude Opus 4.7 is removed today, July 24. Any speed: 'fast' calls need to move over to Opus 4.8●AGENTS — When you create a Managed Agents session, pass agent_with_overrides to swap the model, system prompt, tools, MCP servers, or skills for that single session●KEYS — API keys in the Claude Console can now carry an expiration (a preset, a custom duration, or Never), with an email reminder before keys that live 7 days or longer expire●MCP — Admins can provision MCP connectors org-wide through their identity provider, starting with Okta, so users get connector access automatically on first login●WORKBENCH — The legacy Workbench at platform.claude.com/workbench retires on August 17, along with the experimental prompt generate, improve, and templatize APIs●REVIEW — Claude Code adds a background /code-review so you can keep working while a review runs, with better MCP and Windows path handling alongside●FASTEND — Fast mode for Claude Opus 4.7 is removed today, July 24. Any speed: 'fast' calls need to move over to Opus 4.8
Swapping Agent Config Per Session From One Shared Definition
A design pattern for running one base Managed Agent and overriding its model, prompt, tools, MCP servers, and skills per session with agent_with_overrides — with a validated factory and the operational traps I hit along the way.
As an indie developer, I run four blog sites with agents that behave almost identically.
The only differences are part of the system prompt, which MCP connectors they reach, and which skills they load. And yet, for a while, I kept a completely separate agent definition for each site.
Every time I fixed something shared, I had to sync four places by hand. Fix one and forget another, and a single night's batch would run on a stale prompt. Watching near-identical definitions drift apart, little by little, quietly wears you down.
Managed Agents' agent_with_overrides finally let me collapse this. One base agent definition. Then, only at the moment a session is created, I swap the model, system prompt, tools, MCP servers, and skills. No new definitions — just a coat of paint applied at runtime.
This article shares how I shaped that swapping into a form where nothing goes wrong, along with the validation code I actually use.
Why "swap per session" beats "add more definitions"
I had three real options.
Approach
Syncing shared parts
Clarity of the diff
Risk of a permissions accident
Separate definition per site
Manual, prone to drift
Good (independent)
Low, but definitions explode
One definition, branch inside the prompt
None
Poor (bloated)
Medium (blurry boundaries)
One definition, override per session
None
Good (diff is explicit)
Low if designed for it
What decided it was that the diff gathers into one place, as code.
"For this site, only this MCP and this skill" — that difference lines up right before session creation, in a form you can read. Far clearer than burying conditional branches deep inside a prompt.
But this approach has one thing you will trip over first.
The counterintuitive part: overrides replace, they do not merge
At first I assumed an override was "the base plus a diff."
The base agent allows four tools. In one session I wanted to swap one of them for a different tool, so I passed just that one tool in tools.
The result: the other three tools vanished for that session.
A field you specify in agent_with_overrides replaces that whole field. Even for arrays, elements are not merged individually. Pass a single element to tools, and that session has exactly one tool.
What you pass in the override
Result for that session
Omit the field
Inherits the base value as-is
Empty array []
That element becomes "none" (not inherited)
Only some of the elements
Only the passed elements remain (the rest are gone)
Once stated, it is obvious behavior. But if you let the word "diff" pull you along, you end up with a night batch running with silently fewer tools. I only noticed from the logs.
The principle I took from this is simple. Always pass the final shape of the field. If you want partial add or remove, read the base value, compose it on your side, then pass the result. Composing that safely is the job of the factory below.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦A complete, validated session factory that makes per-session overrides safe
✦Why overrides replace rather than merge, and the trap that silently dropped my tools
✦How to decide override granularity so one base agent can drive four sites
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
I concentrate both the intent of the swap and its safety net into the code that creates the session. Here is the implementation in TypeScript (Agent SDK).
import Anthropic from "@anthropic-ai/sdk";const client = new Anthropic();// The ceiling of what the base agent may allow. Stop if an override exceeds it.const TOOL_ALLOWLIST = new Set([ "web_search", "bash", "text_editor",]);const MCP_ALLOWLIST = new Set([ "mcp-claudelab", "mcp-gemilab", "mcp-antigravitylab", "mcp-rorklab",]);type SiteOverride = { site: string; systemPrompt: string; tools: string[]; // final shape for this session mcpServers: string[]; // final shape for this session skills: string[]; model?: string;};function assertSubset(name: string, given: string[], allow: Set<string>) { const stray = given.filter((v) => !allow.has(v)); if (stray.length > 0) { // Fail before launch on any unexpected escalation of scope throw new Error( `[override guard] ${name} has unpermitted items: ${stray.join(", ")}` ); }}export function buildSessionAgent(base: string, o: SiteOverride) { // Because overrides replace, validate the boundary on our side first assertSubset("tools", o.tools, TOOL_ALLOWLIST); assertSubset("mcpServers", o.mcpServers, MCP_ALLOWLIST); if (o.tools.length === 0) { // Empty means "no tools." I want to suspect an accident, so log it. console.warn(`[override] ${o.site}: tools is empty. Confirm this is intentional`); } const overridden = { type: "agent_with_overrides" as const, agent_id: base, overrides: { model: o.model, // omit to inherit the base model system: o.systemPrompt, tools: o.tools.map((name) => ({ name })), mcp_servers: o.mcpServers, skills: o.skills, }, }; // One line recording what was swapped, so behavior can be traced later. console.info( `[session] site=${o.site} model=${o.model ?? "(inherit)"} ` + `tools=${o.tools.length} mcp=${o.mcpServers.join("+")}` ); return overridden;}
Three points matter.
First, assertSubset checks the ceiling the base allows, before launch. Because overrides replace, a stray unregistered MCP name could point one session at an unintended target. If it falls outside the allowlist, we fail before the session is created.
Second, we warn instead of swallowing an empty array. "No tools" is a valid setting, but in my experience it is a slip nine times out of ten. Not passing it silently saves next morning's regret.
Third, we record what was swapped in a single log line. Because overrides evaporate with the session, the log is the only trace of "which config did that batch run on."
Composing and calling from the session side
With the factory in place, each site's diff becomes short — and readable.
const SITE_OVERRIDES: Record<string, SiteOverride> = { claudelab: { site: "claudelab", systemPrompt: "You are the editor of a Claude tech blog. Write politely.", tools: ["web_search", "text_editor"], mcpServers: ["mcp-claudelab"], skills: ["article-gate", "templating-gate"], }, rorklab: { site: "rorklab", systemPrompt: "You are the editor of a Rork app-dev blog. Write politely.", tools: ["web_search", "text_editor", "bash"], // Rork uses bash for build checks mcpServers: ["mcp-rorklab"], skills: ["article-gate"], model: "claude-opus-4-8", },};async function runFor(site: string) { const override = SITE_OVERRIDES[site]; if (!override) throw new Error(`undefined site: ${site}`); const session = await client.beta.messages.sessions.create({ agent: buildSessionAgent("agent_blog_base", override), }); return session;}
The base agent_blog_base is the only definition. Shared behavior — banned words, the baseline tone policy — is written once, in that base. What varies per site is now confined to a few rows of this table.
Adding a new site is one more entry in the table. No more cloning a whole definition file and fretting over keeping the shared parts in sync.
Operational lessons the docs do not cover
After a few weeks of running this, a few things clicked.
Overrides are scoped to the session, so you need your own inventory. The base definition is listable from the Console, but looking at the base tells you nothing about which config each session actually ran on. I aggregate the log above daily, keeping a record of "which site ran on which model, how many times." If you allow swapping, you have to own the record of the swaps. Skip this, and both cost attribution and root-cause analysis fall apart.
Model inheritance is convenient but can become unintended coupling. Omit model and the session inherits the base model. Raise the base model and every site rises at once. Handy, but when you want to test one site on an older model, you must pin it explicitly rather than omit. I split "safe to inherit" from "must be pinned" inside the table, and always write model on the pinned side.
Set override granularity by change frequency, and it settles. Things that change often — parts of the prompt, referenced connectors — go into overrides. Things that rarely change — banned words, the core output-format policy — stay in the base. Drawing that line by frequency removed my hesitation about "where should I fix this." When a frequently touched diff is mixed into the base, the psychological barrier to touching shared parts rises.
How to choose
My situational take.
If you have two or three bases with large differences, don't force them into overrides; separate definitions are fine. The cost of validating swaps outweighs the cost of syncing.
But if you effectively have one base, and the differences fit into "part of the prompt, connectors, skills," session overrides shine. My four sites were exactly this shape.
And if you do introduce swapping, build validation and recording in from the start. Overrides are powerful precisely because they change permissions and config silently. Decide the boundary you want to enforce — something like assertSubset — up front, and you can hand the night's automated runs over with peace of mind.
I am still learning the feel of this as I operate it. If it gives anyone juggling several similar agents a nudge toward collapsing their design, I would be glad. Thank you for reading.
Share
Thank You for Reading
Claude Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.