Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
e154953
feat(deepagents, code): lean system prompt by default, restorable
nick-hollon-lc Jul 20, 2026
ce23402
refactor(deepagents, code): make _builtin_middleware_prompts private
nick-hollon-lc Jul 20, 2026
8d71dde
fix(deepagents, quickjs): keep skills/memory prompts on the lean default
nick-hollon-lc Jul 20, 2026
404d8f0
fix(deepagents): keep filesystem host-path routing when prose is supp…
nick-hollon-lc Jul 20, 2026
3ac886f
refactor(deepagents, code): public trim_duplicate_tool_prompts flag
nick-hollon-lc Jul 20, 2026
a08b327
refactor(deepagents, code): trim duplicate tool prose unconditionally
nick-hollon-lc Jul 21, 2026
36a22cd
refactor(deepagents): move essential trimmed guidance into tool descr…
nick-hollon-lc Jul 21, 2026
40880b3
fix(deepagents): correct offloaded-result grep guidance for artifacts…
nick-hollon-lc Jul 21, 2026
688d82c
refactor(deepagents): trim tool prose via lean middleware defaults
nick-hollon-lc Jul 21, 2026
cb90572
refactor(deepagents): delete the preserved built-in tool-prompt const…
nick-hollon-lc Jul 21, 2026
fb9c3d1
refactor(deepagents): disable default summarization prompt
nick-hollon-lc Jul 21, 2026
5b50172
refactor(deepagents): delete preserved summarization prompt
nick-hollon-lc Jul 21, 2026
1ab4c4a
test(code): refresh system prompt snapshot
nick-hollon-lc Jul 21, 2026
c9dbda2
chore(quickjs): sync lockfile with deepagents metadata
nick-hollon-lc Jul 21, 2026
4d825bb
fix(deepagents): address prompt middleware review feedback
nick-hollon-lc Jul 22, 2026
5d222b1
Merge remote-tracking branch 'origin/main' into nh/remove-base-system…
nick-hollon-lc Jul 22, 2026
c3c0ad0
Merge remote-tracking branch 'origin/main' into nh/remove-base-system…
nick-hollon-lc Jul 22, 2026
90ab0c5
test(sdk): rename base prompt test names
nick-hollon-lc Jul 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -250,53 +250,7 @@ When using the write_todos tool:
The todo list is a planning tool - use it judiciously to avoid overwhelming the user with excessive task tracking.


## `write_todos`

You have access to the `write_todos` tool to help you manage and plan complex objectives.
Use this tool for complex objectives to ensure that you are tracking each necessary step.
This tool is very helpful for planning complex objectives, and for breaking down these larger complex objectives into smaller steps.

It is critical that you mark todos as completed as soon as you are done with a step. Do not batch up multiple steps before marking them as completed.
For simple objectives that only require a few steps, it is better to just complete the objective directly and NOT use this tool.
Writing todos takes time and tokens, use it when it is helpful for managing complex many-step problems! But not for simple few-step requests.

## Important To-Do List Usage Notes to Remember

- The `write_todos` tool should never be called multiple times in parallel.
- Don't be afraid to revise the To-Do list as you go. New information may reveal new tasks that need to be done, or old tasks that are irrelevant.

## Finishing a task

When you finish all work, write your final answer in the message AFTER your last `write_todos` call — not in the same turn as that call. Start the final message with the substantive content the user asked for — the data, computation, summary, or analysis. The user wants the result, not confirmation that the work is done.

## Following Conventions

- Read files before editing — understand existing content before making changes
- Mimic existing style, naming conventions, and patterns

## Filesystem Tools `ls`, `read_file`, `write_file`, `edit_file`, `delete`, `glob`, `grep`

You have access to a filesystem which you can interact with using these tools.
All file paths must start with a /. Follow the tool docs for the available tools, and use pagination (offset/limit) when reading large files.

- ls: list files in a directory (requires absolute path)
- read_file: read a file from the filesystem
- write_file: write to a file in the filesystem
- edit_file: edit a file in the filesystem
- delete: delete a file or directory (recursively) from the filesystem
- glob: find files matching a pattern (e.g., "**/*.py")
- grep: search for text within files

## Large Tool Results

When a tool result is too large, it may be offloaded into the filesystem instead of being returned inline. In those cases, use `read_file` to inspect the saved result in chunks, or use `grep` within `<tmp_path>/dcode-artifacts/large_tool_results/` if you need to search across offloaded tool results and do not know the exact file path. Offloaded tool results are stored under `<tmp_path>/dcode-artifacts/large_tool_results/<tool_call_id>`.

## Execute Tool `execute`

You have access to an `execute` tool for running shell commands in a sandboxed environment.
Use this tool to run commands, scripts, tests, builds, and other shell operations.

- execute: run a shell command in the sandbox (returns output and exit code)

## Shell paths vs. virtual paths

Expand All @@ -313,42 +267,6 @@ Host path mappings:
- `<tmp_path>/dcode-artifacts/conversation_history/` -> `<tmp_path>/.deepagents/conversation_history/` (e.g. `<tmp_path>/dcode-artifacts/conversation_history/dir/x.py` -> `<tmp_path>/.deepagents/conversation_history/dir/x.py`)
- `/dcode-artifacts-fallback/conversation_history/` -> `<tmp_path>/.deepagents/conversation_history/` (e.g. `/dcode-artifacts-fallback/conversation_history/dir/x.py` -> `<tmp_path>/.deepagents/conversation_history/dir/x.py`)

## `task` (subagent spawner)

You have access to a `task` tool to launch short-lived subagents that handle isolated tasks. These agents are ephemeral — they live only for the duration of the task and return a single result.

When to use the task tool:

- When a task is complex and multi-step, and can be fully delegated in isolation
- When a task is independent of other tasks and can run in parallel
- When a task requires focused reasoning or heavy token/context usage that would bloat the orchestrator thread
- When sandboxing improves reliability (e.g. code execution, structured searches, data formatting)
- When you only care about the output of the subagent, and not the intermediate steps (ex. performing a lot of research and then returned a synthesized report, performing a series of computations or lookups to achieve a concise, relevant answer.)

Subagent lifecycle:

1. **Spawn** → Provide clear role, instructions, and expected output
2. **Run** → The subagent completes the task autonomously
3. **Return** → The subagent provides a single structured result
4. **Reconcile** → Incorporate or synthesize the result into the main thread

When NOT to use the task tool:

- If you need to see the intermediate reasoning or steps after the subagent has completed (the task tool hides them)
- If the task is trivial (a few tool calls or simple lookup)
- If delegating does not reduce token usage, complexity, or context switching
- If splitting would add latency without benefit

## Important Task Tool Usage Notes to Remember

- Whenever possible, parallelize the work that you do. This is true for both tool_calls, and for tasks. Whenever you have independent steps to complete - make tool_calls, or kick off tasks (subagents) in parallel to accomplish them faster. This saves time for the user, which is incredibly important.
- Remember to use the `task` tool to silo independent tasks within a multi-part objective.
- You should use the `task` tool whenever you have a complex task that will take multiple steps, and is independent from other tasks that the agent needs to complete. These agents are highly competent and efficient.

Available subagent types:

- general-purpose: General-purpose agent for researching complex questions, searching for files and content, and executing multi-step tasks. When you are searching for a keyword or file and are not confident that you will find the right match in the first few tries use this agent to perform the search for you. This agent has access to all tools as the main agent.

## Goal and Rubric Tools

Use `get_rubric` to inspect active acceptance criteria before deciding whether work is
Expand Down Expand Up @@ -502,11 +420,3 @@ Remember: Skills make you more capable and consistent. When in doubt, check if a
**Project**: python (uv), monorepo

**Runtimes**: Python 3.13.1, Node 24.14.0

## Compact conversation Tool `compact_conversation`

You have access to a `compact_conversation` tool. This tool refreshes your context window to reduce context bloat and costs.

You should use the tool when:
- The user asks to move on to a completely new task for which previous context is likely irrelevant.
- You have finished extracting or synthesizing a result and previous working context is no longer needed.
2 changes: 2 additions & 0 deletions libs/deepagents/deepagents/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from deepagents._version import __version__
from deepagents.graph import (
BASE_AGENT_PROMPT,
Comment thread
nick-hollon-lc marked this conversation as resolved.
DeepAgentState,
SystemPromptConfig,
create_deep_agent,
Expand All @@ -27,6 +28,7 @@
)

__all__ = [
"BASE_AGENT_PROMPT",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idt we should export this

"AsyncSubAgent",
"AsyncSubAgentMiddleware",
"CompiledSubAgent",
Expand Down
62 changes: 24 additions & 38 deletions libs/deepagents/deepagents/graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,29 +116,6 @@ class DeepAgentState(AgentState):
## Progress Updates

For longer tasks, provide brief progress updates at reasonable intervals — a concise sentence recapping what you've done and what's next.""" # noqa: E501
"""Default base system prompt for every deep agent.

The final system prompt sent to the model is assembled, in order, from:

1. `prefix` — caller text placed before the base (the `system_prompt=`
argument, or its `prefix` key). Always first, so caller instructions
take precedence.
2. `base` — this constant by default; replaced by the `system_prompt`
config's `base` key, or (when that key is absent) by
`HarnessProfile.base_system_prompt`. Setting `base` to `None` drops it.
3. `suffix` — caller text placed after the base (the `system_prompt`
config's `suffix` key).
4. `HarnessProfile.system_prompt_suffix` — model-tuning guidance appended
last.

Parts are joined by blank lines (`\\n\\n`). When any part is a
`SystemMessage`, the result is a `SystemMessage` whose `content_blocks`
concatenate each part's blocks (with `\\n\\n` text separators), preserving
any `cache_control` markers.

See `create_deep_agent`'s `system_prompt` parameter and
[`SystemPromptConfig`][deepagents.SystemPromptConfig].
"""


class SystemPromptConfig(TypedDict, total=False):
Expand All @@ -152,12 +129,11 @@ class SystemPromptConfig(TypedDict, total=False):
"""Text placed before the base prompt."""

base: str | SystemMessage | None
"""Replacement for the built-in base prompt.
"""Replacement for the active profile base prompt.

Omit the key to keep the built-in base (or the active
`HarnessProfile.base_system_prompt`). Set it to `None` to drop the base
entirely, leaving only `prefix`, `suffix`, and middleware-contributed
content.
Omit the key to keep the active `HarnessProfile.base_system_prompt`, if
any. Set it to `None` to drop the base entirely, leaving only `prefix`,
`suffix`, and middleware-contributed content.
"""

suffix: str | SystemMessage | None
Expand Down Expand Up @@ -409,15 +385,14 @@ def create_deep_agent( # noqa: C901, PLR0912, PLR0915 # Complex graph assembly
system_prompt: Custom system instructions.

A `str` or `SystemMessage` is placed at the front of the system
prompt, before the SDK's default base prompt and any model-tuning
suffix from a registered `HarnessProfile` (`system_prompt=None`
uses the default base on its own).
prompt, before any base or model-tuning suffix from a registered
`HarnessProfile`.

For more control, pass a
[`SystemPromptConfig`][deepagents.SystemPromptConfig] with any of:

- `prefix`: text before the base (same as passing a bare string).
- `base`: replace the built-in base prompt; omit the key to keep
- `base`: replace the profile base prompt; omit the key to keep
it, or set it to `None` to drop the base entirely.
- `suffix`: text after the base (before any profile suffix).

Expand Down Expand Up @@ -694,6 +669,18 @@ class MyState(DeepAgentState):

backend = backend if backend is not None else StateBackend()

# The built-in tool-usage guidance prose duplicates the tools' own schema
# descriptions, so the deepagents-owned middleware (filesystem / subagent /
# async-subagent) default to emitting none of it; only the essential dynamic
# bits remain (filesystem's host-path routing, empty for non-composite
# backends; the available-agent list, which reaches the model through the
# `task` tool / async tools). `TodoListMiddleware` is from langchain and
# defaults to its full prompt, so it is the one middleware passed
# `system_prompt=""` here to trim it. Skills and Memory keep their fragment:
# it is the only channel that surfaces the loaded skill index / memory
# content, and both are built only when the caller passes `skills=` /
# `memory=`.

# Process caller-supplied subagents first so the decision of whether to
# auto-add the default general-purpose subagent can factor in an explicit
# override, and so its middleware stack (including any factory-based
Expand Down Expand Up @@ -721,7 +708,7 @@ class MyState(DeepAgentState):

# Build middleware: base stack + skills (if specified) + user's middleware
subagent_middleware: list[AgentMiddleware[Any, Any, Any]] = [
TodoListMiddleware(),
TodoListMiddleware(system_prompt=""),
FilesystemMiddleware(
backend=backend,
custom_tool_descriptions=_subagent_profile.tool_description_overrides,
Expand Down Expand Up @@ -807,7 +794,7 @@ class MyState(DeepAgentState):
gp_profile = _profile.general_purpose_subagent or GeneralPurposeSubagentProfile()
if gp_profile.enabled is not False and not any(spec["name"] == GENERAL_PURPOSE_SUBAGENT["name"] for spec in inline_subagents):
gp_middleware: list[AgentMiddleware[Any, Any, Any]] = [
TodoListMiddleware(),
TodoListMiddleware(system_prompt=""),
FilesystemMiddleware(
backend=backend,
custom_tool_descriptions=_profile.tool_description_overrides,
Expand Down Expand Up @@ -873,7 +860,7 @@ class MyState(DeepAgentState):

# Build main agent middleware stack
deepagent_middleware: list[AgentMiddleware[Any, Any, Any]] = [
TodoListMiddleware(),
TodoListMiddleware(system_prompt=""),
]
if skills is not None:
deepagent_middleware.append(SkillsMiddleware(backend=backend, sources=skills))
Expand Down Expand Up @@ -969,14 +956,13 @@ class MyState(DeepAgentState):
)

# Assemble the main-agent prompt: prefix -> base -> suffix -> profile suffix.
# The config's `base` (when the key is present) overrides the profile base;
# otherwise the profile base, then BASE_AGENT_PROMPT, is used.
# The config's `base` (when the key is present) overrides the profile base.
cfg = _normalize_system_prompt(system_prompt)
prompt_parts: list[str | SystemMessage] = []
prefix = cfg.get("prefix")
if prefix is not None:
prompt_parts.append(prefix)
profile_base = _profile.base_system_prompt if _profile.base_system_prompt is not None else BASE_AGENT_PROMPT
profile_base = _profile.base_system_prompt
base = cfg.get("base", profile_base)
if base is not None:
prompt_parts.append(base)
Expand Down
53 changes: 9 additions & 44 deletions libs/deepagents/deepagents/middleware/async_subagents.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,47 +175,6 @@ class ListAsyncTasksSchema(BaseModel):
4. Multiple async subagents can run concurrently — launch several and let them run in the background.
5. The subagent runs on a remote server, so it has its own tools and capabilities.""" # noqa: E501

ASYNC_TASK_SYSTEM_PROMPT = """## Async subagents (remote LangGraph servers)

You have access to async subagent tools that launch background tasks on remote LangGraph servers.

### Tools

- `start_async_task`: Start a new background task. Returns a task ID immediately.
- `check_async_task`: Get current status and result of a task. Returns status + result (if complete).
- `update_async_task`: Send new instructions to a running task. Returns confirmation + updated status.
- `cancel_async_task`: Stop a running task. Returns confirmation.
- `list_async_tasks`: List all tracked tasks with live statuses. Returns summary of all tasks.

### Workflow

1. **Start** — Use `start_async_task` to start a task. Report the task ID to the user and stop.
Do NOT immediately check the status — the task runs in the background while you and the user continue other work.
2. **Check (on request)** — Only use `check_async_task` when the user explicitly asks for a status update or
result. If the status is "running", report that and stop — do not poll in a loop.
3. **Update** (optional) — Use `update_async_task` to send new instructions to a running task. This interrupts
the current run and starts a fresh one on the same thread. The task_id stays the same.
4. **Cancel** (optional) — Use `cancel_async_task` to stop a task that is no longer needed.
5. **Collect** — When `check_async_task` returns status "success", the result is included in the response.
6. **List** — Use `list_async_tasks` to see live statuses for all tasks at once, or to recall task IDs after context compaction.

### Critical rules

- After launching, ALWAYS return control to the user immediately. Never auto-check after launching.
- Never poll `check_async_task` in a loop. Check once per user request, then stop.
- If a check returns "running", tell the user and wait for them to ask again.
- Task statuses in conversation history are ALWAYS stale — a task that was "running" may now be done.
NEVER report a status from a previous tool result. ALWAYS call a tool to get the current status:
use `list_async_tasks` when the user asks about multiple tasks or "all tasks",
use `check_async_task` when the user asks about a specific task.
- Always show the full task_id — never truncate or abbreviate it.

### When to use async subagents

- Long-running tasks that would block the main agent
- Tasks that benefit from running on specialized remote deployments
- When you want to run multiple tasks concurrently and collect results later"""


def _resolve_headers(spec: AsyncSubAgent) -> dict[str, str]:
"""Build headers for a remote Agent Protocol server.
Expand Down Expand Up @@ -497,7 +456,11 @@ async def acheck_async_task(
name="check_async_task",
func=check_async_task,
coroutine=acheck_async_task,
description="Check the status of an async subagent task. Returns the current status and, if complete, the result.",
description=(
"Check the status of an async subagent task. Returns the current status and, if complete, the result. "
"Statuses shown earlier in the conversation are always stale, so call this to get the current status "
"rather than reporting a status from a previous tool result."
),
infer_schema=False,
args_schema=CheckAsyncTaskSchema,
)
Expand Down Expand Up @@ -833,7 +796,9 @@ async def alist_async_tasks(
"List tracked async subagent tasks with their current live statuses. "
"By default shows all tasks. Use `status_filter` to narrow by status "
"(e.g. 'running', 'success', 'error', 'cancelled'). "
"Use `check_async_task` to get the full result of a specific completed task."
"Use `check_async_task` to get the full result of a specific completed task. "
"Statuses shown earlier in the conversation are always stale, so call this to read current "
"statuses rather than reporting one from a previous tool result."
),
infer_schema=False,
args_schema=ListAsyncTasksSchema,
Expand Down Expand Up @@ -911,7 +876,7 @@ def __init__(
self,
*,
async_subagents: list[AsyncSubAgent],
system_prompt: str | None = ASYNC_TASK_SYSTEM_PROMPT,
system_prompt: str | None = None,
) -> None:
"""Initialize the `AsyncSubAgentMiddleware`."""
super().__init__()
Expand Down
Loading