diff --git a/pipeline/preprocessors/link_map.py b/pipeline/preprocessors/link_map.py
index 55b2f6ae42..05ac31a567 100644
--- a/pipeline/preprocessors/link_map.py
+++ b/pipeline/preprocessors/link_map.py
@@ -35,6 +35,7 @@ class LinkMap(TypedDict):
# Deep Agents
"create_deep_agent": "deepagents/graph/create_deep_agent",
"DeepAgentState": "deepagents/graph/DeepAgentState",
+ "SystemPromptConfig": "deepagents/graph/SystemPromptConfig",
"SubAgent": "deepagents/middleware/subagents/SubAgent",
"CompiledSubAgent": "deepagents/middleware/subagents/CompiledSubAgent",
"SubAgentMiddleware": "deepagents/middleware/subagents/SubAgentMiddleware",
diff --git a/src/oss/deepagents/customization.mdx b/src/oss/deepagents/customization.mdx
index 5c343c33d6..e3a3ad07ca 100644
--- a/src/oss/deepagents/customization.mdx
+++ b/src/oss/deepagents/customization.mdx
@@ -31,6 +31,7 @@ import CustomizationToolsPy from '/snippets/code-samples/customization-tools-py.
import CustomizationToolsJs from '/snippets/code-samples/customization-tools-js.mdx';
import CustomizationSystemPromptPy from '/snippets/code-samples/customization-system-prompt-py.mdx';
import CustomizationSystemPromptJs from '/snippets/code-samples/customization-system-prompt-js.mdx';
+
import CustomizationMiddlewarePy from '/snippets/code-samples/customization-middleware-py.mdx';
import CustomizationMiddlewareJs from '/snippets/code-samples/customization-middleware-js.mdx';
import CustomizationMiddlewareDoPy from '/snippets/code-samples/customization-middleware-do-py.mdx';
@@ -52,7 +53,6 @@ import CustomizationOverviewPy from '/snippets/code-samples/customization-overvi
import CustomizationOverviewJs from '/snippets/code-samples/customization-overview-js.mdx';
import CustomizationMcpPy from '/snippets/code-samples/customization-mcp-py.mdx';
import CustomizationMcpJs from '/snippets/code-samples/customization-mcp-js.mdx';
-import CustomizationPromptAssemblyPy from '/snippets/code-samples/customization-prompt-assembly-py.mdx';
import CustomizationGpSubagentProfilePy from '/snippets/code-samples/customization-gp-subagent-profile-py.mdx';
Build the harness around your goal. `create_deep_agent` gives you a production-ready foundation: connect it to your data, shape its behavior, and add the capabilities your use case needs.
@@ -177,11 +177,7 @@ For detailed configuration options including stdio servers, OAuth authentication
## System prompt
-Deep Agents come with a built-in system prompt. A deep agent's value comes from the orchestration layer the SDK provides on top of the model—planning, virtual-filesystem tools, and subagents—and the model needs to know those exist and when to reach for them. The built-in prompt teaches the agent how to use that scaffolding so you don't have to re-derive it for every project; tweak it through a [profile](/oss/deepagents/profiles#harness-profiles) or your own `system_prompt=` rather than copying it verbatim.
-
-When middleware add special tools, like the filesystem tools, it appends them to the system prompt.
-
-Each deep agent should also include a custom system prompt specific to its specific use case:
+Deep Agents ship with a built-in base system prompt that teaches the agent how to use the harness scaffolding (planning, filesystem tools, subagents). Pass `system_prompt=` to prepend your own instructions before that base prompt:
:::python
@@ -191,64 +187,41 @@ Each deep agent should also include a custom system prompt specific to its speci
:::
-### Prompt assembly
-
-Deep Agents builds the system prompt from up to four named parts so that caller-supplied instructions, the SDK's built-in agent guidance, and any model-specific [profile](/oss/deepagents/profiles) overrides can coexist with predictable precedence. Without this layering, a profile suffix tuned for Claude (for example) could overwrite or be overwritten by your `system_prompt=` argument depending on call order; the named slots make the ordering explicit and stable.
-
-In practice, most callers only encounter two slots: `USER` (your `system_prompt=`) and `BASE` (the SDK default). Selecting a model with a built-in profile—Anthropic or OpenAI today—adds a `SUFFIX`. The full four-part assembly is mainly relevant when you author a custom `HarnessProfile` or debug why a profile's text appears where it does.
+When middleware adds special tools, like the filesystem tools, it appends its own guidance to the system prompt at runtime.
-The four named parts (each may be absent):
-
-| Name | Source | Notes |
-| -------- | ------------------------------------------------- | ------------------------------------------------- |
-| `USER` | `system_prompt=` argument to `create_deep_agent` | `str` or `SystemMessage`; omitted when unset. |
-| `BASE` | The SDK default (`BASE_AGENT_PROMPT`) | Always present unless replaced by a profile's `CUSTOM`. |
-| `CUSTOM` | [`HarnessProfile.base_system_prompt`](/oss/deepagents/profiles#harness-profiles) | Replaces `BASE` outright when a matching profile sets it. |
-| `SUFFIX` | [`HarnessProfile.system_prompt_suffix`](/oss/deepagents/profiles#harness-profiles) | Appended last when a matching profile sets it. |
+:::python
+
+`SystemPromptConfig` requires `deepagents>=0.7.0a6`.
+
-The order is always **`USER` -> (`BASE` or `CUSTOM`) -> `SUFFIX`**, joined by blank lines (`\n\n`). Two invariants follow:
+For full control over prompt assembly, pass a @[`SystemPromptConfig`] dict with `prefix`, `base`, and `suffix` keys:
-1. **`USER` is always at the front.** The caller's text precedes any SDK or profile content, so persona/instructions take precedence regardless of which model is selected.
-2. **`SUFFIX` is always at the end.** Profile suffixes sit closest to the conversation history, where model-tuning guidance lands most reliably.
+- **`prefix`**: text placed before the base prompt (same as passing a bare string).
+- **`base`**: replaces the built-in base prompt. Omit the key to keep the built-in base, or set it to `None` to drop the base entirely.
+- **`suffix`**: text placed after the base prompt.
-Assembled shapes (✓ = field is set, - = field is unset):
+Parts are assembled in order: `prefix` -> `base` -> `suffix` -> any model-specific [profile](/oss/deepagents/profiles) suffix. Each part accepts a `str` or a `SystemMessage` (to preserve `cache_control` markers for Anthropic prompt caching).
-| `system_prompt=` | profile `base_system_prompt` (`CUSTOM`) | profile `system_prompt_suffix` (`SUFFIX`) | Final assembled system prompt |
-| ---------------- | :-------------------------------------: | :---------------------------------------: | ----------------------------- |
-| `None` | - | - | `BASE` |
-| `None` | - | ✓ | `BASE` + `SUFFIX` |
-| `None` | ✓ | - | `CUSTOM` |
-| `None` | ✓ | ✓ | `CUSTOM` + `SUFFIX` |
-| `str` | - | - | `USER` + `BASE` |
-| `str` | - | ✓ | `USER` + `BASE` + `SUFFIX` |
-| `str` | ✓ | - | `USER` + `CUSTOM` |
-| `str` | ✓ | ✓ | `USER` + `CUSTOM` + `SUFFIX` |
+```python
+# Overwrite the default base prompt:
+create_deep_agent(..., system_prompt={"base": "..."})
-Worked example—built-in profiles (Anthropic, OpenAI) ship only a `system_prompt_suffix`, so a typical call lands in the `str` + `-` + `✓` row:
+# No system prompt except from middleware:
+create_deep_agent(..., system_prompt={"base": None})
-
+# Sandwich the default base prompt:
+create_deep_agent(..., system_prompt={"prefix": "...", "suffix": "..."})
+```
-
- Passing a `SystemMessage` (rather than a string) triggers a different concatenation path: the right-hand assembly (`BASE`-or-`CUSTOM` plus any `SUFFIX`) is appended as an additional text content block onto the message's existing `content_blocks`. The same logical ordering applies (caller blocks first), and any `cache_control` markers on the caller's blocks are preserved—useful for placing explicit Anthropic prompt-cache breakpoints.
-
+For model-specific system prompt customization, such as replacing the base prompt or appending a suffix for a particular provider, use a [harness profile](/oss/deepagents/profiles#harness-profiles).
+:::
- The [prompt assembly](#prompt-assembly) overlay rules also apply to declarative [subagents](/oss/deepagents/subagents): each subagent re-runs profile resolution against **its own model**, then applies the resolved profile's `base_system_prompt` / `system_prompt_suffix` to its authored `system_prompt`. The subagent's `system_prompt` plays the `BASE` role; `CUSTOM` and `SUFFIX` come from the profile that matches the subagent's model (which may differ from the main agent's profile).
-
- | `spec["system_prompt"]` | profile `base_system_prompt` (`CUSTOM`) | profile `system_prompt_suffix` (`SUFFIX`) | Final subagent system prompt |
- | ----------------------- | :-------------------------------------: | :---------------------------------------: | ---------------------------- |
- | authored | - | - | authored |
- | authored | - | ✓ | authored + `SUFFIX` |
- | authored | ✓ | - | `CUSTOM` |
- | authored | ✓ | ✓ | `CUSTOM` + `SUFFIX` |
-
- There is no `USER` segment for subagents. The spec's authored `system_prompt` is the closest analog and stays in the `BASE` slot. A profile that ships only a `system_prompt_suffix` (the common case for built-in Anthropic / OpenAI profiles) just appends to whatever the subagent author wrote. A profile that sets `base_system_prompt` will *replace* the authored prompt outright.
+ Declarative [subagents](/oss/deepagents/subagents) resolve profile overlays against their own model, then apply the resolved profile's `base_system_prompt` / `system_prompt_suffix` to the subagent's authored `system_prompt`. A profile that ships only a `system_prompt_suffix` (the common case for built-in Anthropic / OpenAI profiles) appends to the authored prompt. A profile that sets `base_system_prompt` replaces it outright.
- The auto-added [general-purpose subagent](/oss/deepagents/subagents#the-general-purpose-subagent) follows the [prompt assembly](#prompt-assembly) overlay rules with one extra layer: the GP base prompt is resolved as **`general_purpose_subagent.system_prompt` (if set) -> `HarnessProfile.base_system_prompt` (if set) -> SDK general-purpose default**. The profile suffix layers on top either way.
-
- The two override fields can both carry a base-prompt replacement, but they are not interchangeable. `general_purpose_subagent.system_prompt` is general-purpose-specific configuration; `base_system_prompt` is a global override that primarily targets the main agent. When both are set, the **general-purpose-specific intent wins for the general-purpose subagent** so a user tuning both fields never sees their GP override silently dropped:
+ The auto-added [general-purpose subagent](/oss/deepagents/subagents#the-general-purpose-subagent) resolves its base prompt as **`general_purpose_subagent.system_prompt` (if set) -> `HarnessProfile.base_system_prompt` (if set) -> SDK general-purpose default**, with the profile suffix layered on top. When both override fields are set, the general-purpose-specific one wins so a caller tuning both fields never sees their GP override silently dropped:
@@ -256,8 +229,6 @@ Worked example—built-in profiles (Anthropic, OpenAI) ship only a `system_promp
| ----- | ------------------- |
| Main agent | `"You are ACME's support orchestrator." + SUFFIX` |
| GP subagent | `"You are a research subagent. Cite sources." + SUFFIX` |
-
- If `general_purpose_subagent.system_prompt` is unset, the GP subagent falls back to `base_system_prompt` (when set) and finally to the SDK general-purpose default.
diff --git a/src/oss/deepagents/profiles.mdx b/src/oss/deepagents/profiles.mdx
index fcbefeb73b..2204e79e35 100644
--- a/src/oss/deepagents/profiles.mdx
+++ b/src/oss/deepagents/profiles.mdx
@@ -42,11 +42,11 @@ A harness profile describes prompt-assembly, tool-visibility, middleware, and de
:::python
- Replace the base Deep Agents system prompt (`CUSTOM` in [Prompt assembly](/oss/deepagents/customization#prompt-assembly)).
+ Replace the base Deep Agents system prompt (the `base` key in [System prompt](/oss/deepagents/customization#system-prompt)).
- Append text to the assembled base prompt (`SUFFIX` in [Prompt assembly](/oss/deepagents/customization#prompt-assembly)); applied to the main agent, declarative subagents, and the auto-added general-purpose subagent.
+ Append text after the caller's `suffix`, placed last in the assembled system prompt. Applied to the main agent, declarative subagents, and the auto-added general-purpose subagent.
@@ -72,11 +72,11 @@ A harness profile describes prompt-assembly, tool-visibility, middleware, and de
:::js
- Replace the base Deep Agents system prompt (`CUSTOM` in [Prompt assembly](/oss/deepagents/customization#prompt-assembly)).
+ Replace the base Deep Agents system prompt (the `base` key in [System prompt](/oss/deepagents/customization#system-prompt)).
- Append text to the assembled base prompt (`SUFFIX` in [Prompt assembly](/oss/deepagents/customization#prompt-assembly)); applied to the main agent, declarative subagents, and the auto-added general-purpose subagent.
+ Append text after the caller's `suffix`, placed last in the assembled system prompt. Applied to the main agent, declarative subagents, and the auto-added general-purpose subagent.
@@ -102,13 +102,13 @@ A harness profile describes prompt-assembly, tool-visibility, middleware, and de
:::python
- Caller-supplied `system_prompt=` always sits at the front of the assembled prompt, and `system_prompt_suffix` always sits at the end—regardless of which model is selected. The same overlay rules apply to subagents: each subagent re-runs profile resolution against its own model. See [Prompt assembly](/oss/deepagents/customization#prompt-assembly) for the full per-case breakdown (main agent, subagents, and the general-purpose subagent).
+ Caller-supplied `system_prompt=` always sits at the front of the assembled prompt, and `system_prompt_suffix` always sits at the end—regardless of which model is selected. The same overlay rules apply to subagents: each subagent re-runs profile resolution against its own model. See [System prompt](/oss/deepagents/customization#system-prompt) for the full per-case breakdown (main agent, subagents, and the general-purpose subagent).
:::
:::js
- Caller-supplied `systemPrompt` always sits at the front of the assembled prompt, and `systemPromptSuffix` always sits at the end—regardless of which model is selected. The same overlay rules apply to subagents: each subagent re-runs profile resolution against its own model. See [Prompt assembly](/oss/deepagents/customization#prompt-assembly) for the full per-case breakdown (main agent, subagents, and the general-purpose subagent).
+ Caller-supplied `systemPrompt` always sits at the front of the assembled prompt, and `systemPromptSuffix` always sits at the end—regardless of which model is selected. The same overlay rules apply to subagents: each subagent re-runs profile resolution against its own model. See [System prompt](/oss/deepagents/customization#system-prompt) for the full per-case breakdown (main agent, subagents, and the general-purpose subagent).
:::
diff --git a/src/oss/deepagents/subagents.mdx b/src/oss/deepagents/subagents.mdx
index 649b72a667..ba405b7808 100644
--- a/src/oss/deepagents/subagents.mdx
+++ b/src/oss/deepagents/subagents.mdx
@@ -414,7 +414,7 @@ For full details on schema types and strategies (tool calling vs. provider-nativ
In addition to any user-defined subagents, every deep agent has access to a `general-purpose` subagent at all times. This subagent:
-- Uses its own [default system prompt with profile overlays applied](/oss/deepagents/customization#prompt-assembly)
+- Uses its own [default system prompt with profile overlays applied](/oss/deepagents/customization#system-prompt)
- Has access to all the same tools
- Uses the same model (unless overridden)
- Inherits skills from the main agent (when skills are configured)
diff --git a/src/oss/python/releases/changelog.mdx b/src/oss/python/releases/changelog.mdx
index 2348d4a3f8..3cc50b55ac 100644
--- a/src/oss/python/releases/changelog.mdx
+++ b/src/oss/python/releases/changelog.mdx
@@ -17,6 +17,7 @@ rss: true
- **`write_file` now overwrites existing files**: `write_file` used to error if the target file already existed. It now overwrites it — use `edit_file` for targeted changes to an existing file.
- **[Override a default middleware instance](/oss/deepagents/customization#override-a-default-middleware-instance)**: A `middleware=` (or subagent `middleware`) instance whose `.name` matches a default now replaces that default in place, instead of erroring on duplicate middleware.
- **[Restrict filesystem tools](/oss/deepagents/overview#virtual-filesystem-access)**: `FilesystemMiddleware` now accepts a `tools` allowlist to expose only a subset of the built-in filesystem tools to the model, building on the middleware-override behavior above.
+ - **[Structured system prompt configuration](/oss/deepagents/customization#system-prompt)**: The `system_prompt` parameter now accepts a `SystemPromptConfig` dict with `prefix`, `base`, and `suffix` keys, enabling callers to replace or remove the built-in base prompt and add text before or after it.