Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
ecd6974
Fix tool_call_id loss in unified memory message round-trip
alcholiclg Aug 4, 2026
f8ce706
Pair tool results with pending calls when tool_call_id is missing in …
alcholiclg Aug 4, 2026
a7b86ec
Seal errored rounds so resume consumes the next prompt instead of rep…
alcholiclg Aug 4, 2026
0686305
Skip LLM call retries for non-retryable 4xx client errors
alcholiclg Aug 4, 2026
977d16b
Dedupe identical per-round error records in SessionLog
alcholiclg Aug 4, 2026
4b5f976
Use native Windows shell semantics in the local code executor
alcholiclg Aug 4, 2026
0ae7224
Infer provider from model name only when no service is configured
alcholiclg Aug 6, 2026
738640e
Ingest a round's memory before the blocking interactive input wait
alcholiclg Aug 10, 2026
ed01753
Release the mem0 vector client on close and log ingestion failures
alcholiclg Aug 10, 2026
18bfe05
Merge branch 'main' of https://github.com/modelscope/ms-agent into fi…
alcholiclg Aug 10, 2026
4335a8b
Let a handler that supports it receive a round's parallel permission …
alcholiclg Aug 10, 2026
c1654a5
Report each parallel tool call's completion as it finishes, not after…
alcholiclg Aug 10, 2026
1100a38
Take memory ingestion off the turn's critical path
alcholiclg Aug 10, 2026
41cfac2
Ingest memory on closing rounds only, and give shared stores an owner…
alcholiclg Aug 10, 2026
313a232
Make mem0 recall size configurable (recall_top_k)
alcholiclg Aug 11, 2026
e2d284b
Build the system prompt from live workspace files (SOUL/AGENTS/PROFIL…
alcholiclg Aug 13, 2026
2712ff7
Attach vector recall durably to each user turn and keep the file back…
alcholiclg Aug 13, 2026
e66e382
Ship agent_hub default configs in the wheel and merge the project con…
alcholiclg Aug 13, 2026
6d07904
Merge remote-tracking branch 'upstream/main' into fix/runtime-robustness
alcholiclg Aug 13, 2026
ec2816c
Remove the memory section from the prompt when memory is cleared or i…
alcholiclg Aug 13, 2026
67a07b1
fix ut
alcholiclg Aug 13, 2026
c715dfb
Fix unified memory losing writes and ignoring config changes
alcholiclg Aug 14, 2026
4815b4b
Merge branch 'fix/memory-config-and-rebuild' into fix/runtime-robustness
alcholiclg Aug 14, 2026
6b5fae5
Retry once with thinking off when a model rejects the thinking parame…
alcholiclg Aug 14, 2026
c9389bc
Lower a single reasoning_effort knob onto each endpoint's own thinkin…
alcholiclg Aug 17, 2026
e8a910c
Send both thinking knobs on DashScope, where the switch and the effor…
alcholiclg Aug 17, 2026
582f0ac
Lower the thinking knob once per request, repair mandatory-thinking f…
alcholiclg Aug 17, 2026
2893443
Adopt the effort vocabulary the endpoints themselves report instead o…
alcholiclg Aug 17, 2026
1f26c86
Clamp a thinking tier downward, never upward, and record only what en…
alcholiclg Aug 17, 2026
e2fa162
Ask MiniMax to deliver reasoning in its own field, the only shape it …
alcholiclg Aug 18, 2026
5ddb09c
Offer only the tiers an endpoint actually has, not the whole ladder
alcholiclg Aug 18, 2026
34bddc0
Match a bare command against its own `<cmd> *` rule
alcholiclg Aug 18, 2026
b39bd5f
Confirm network commands instead of refusing them, and remember only …
alcholiclg Aug 18, 2026
b271839
Stop a bare `*` in dangerous_removal_paths from making every path dan…
alcholiclg Aug 18, 2026
3d0b0e0
Test the remembered pattern through the allow_always path the UI actu…
alcholiclg Aug 18, 2026
20b4288
Send attached images to the model as native image content instead of …
alcholiclg Aug 18, 2026
d9f2428
Merge branch 'feat/multimodal-input' into fix/runtime-robustness
alcholiclg Aug 18, 2026
e614df6
Mark earlier image descriptions as another model's reliable history s…
alcholiclg Aug 18, 2026
8374082
Merge branch 'main' of https://github.com/modelscope/ms-agent into fi…
alcholiclg Aug 19, 2026
89a4c57
Simulate mem0's absence explicitly so the test stops depending on lef…
alcholiclg Aug 19, 2026
f3a1c6b
Resolve image support from the per-model switch, and retry refusals t…
alcholiclg Aug 20, 2026
eb0bbce
Merge branch 'fix/vision-and-stream-retry' into fix/runtime-robustness
alcholiclg Aug 20, 2026
be1b176
Fall back to Tavily's keyless tier when no API key is configured, and…
alcholiclg Aug 25, 2026
2312cc3
Let a tool declare its own output budget so the generic truncator sto…
alcholiclg Aug 25, 2026
8d4cc5f
Correct the comments.
alcholiclg Aug 25, 2026
3cb3076
Merge branch 'main' of https://github.com/modelscope/ms-agent into fi…
alcholiclg Aug 25, 2026
bd6ce98
Add socksio so a SOCKS proxy in the environment does not make every H…
alcholiclg Aug 25, 2026
3944d50
update requirements
alcholiclg Aug 25, 2026
bb945a9
Fix shell policy misfires on redirects, heredocs and temp-dir writes,…
alcholiclg Aug 27, 2026
3d8d2d2
Coerce string-typed arguments to their declared schema types, resolve…
alcholiclg Aug 27, 2026
9ba17f0
Run shell commands verbatim in a non-login shell and inject the agent…
alcholiclg Aug 27, 2026
054166b
Connect MCP servers in parallel under per-server owner tasks with std…
alcholiclg Aug 27, 2026
ae5f3b2
Tell the model which workspace directories are framework-managed so t…
alcholiclg Aug 27, 2026
ed00df9
Ignore the Qoder IDE directory
alcholiclg Aug 27, 2026
14afad6
Merge branch 'main' of https://github.com/modelscope/ms-agent into fi…
alcholiclg Aug 27, 2026
9c7a2aa
Make stdio MCP servers spawnable from minimal-PATH backends with a ne…
alcholiclg Aug 28, 2026
a540fdf
Unify the workspace-records prompt section across project layouts, ad…
alcholiclg Aug 28, 2026
d9d4dff
Point large-output references at .ms_agent/artifacts, where spills ac…
alcholiclg Aug 28, 2026
17d4b55
Merge branch 'main' of https://github.com/modelscope/ms-agent into fi…
alcholiclg Aug 28, 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
71 changes: 44 additions & 27 deletions ms_agent/agent/llm_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -466,44 +466,61 @@ def _build_system_content(self) -> str:
return content

def _build_workspace_internals_section(self) -> str:
"""Describe the framework's own directories, when they sit in the
working directory.

Only for the layout where they do. A project opened from an existing
folder keeps its records elsewhere, and telling that agent to watch out
for a ``sessions/`` directory it will never encounter would be a
fabricated warning.
"""Describe where the framework's own records live.

Which of the two descriptions applies is decided by where the session
log ACTUALLY writes, not by what the working directory happens to
contain: a mounted project may well have a user-owned ``sessions/``
directory of its own, and describing that as framework transcripts
would be false. The directory-existence check is only the fallback for
callers that run without a session log.
"""
from ms_agent.prompting.builtin import WORKSPACE_INTERNALS_HINT
from ms_agent.prompting.builtin import (TRANSCRIPTS_INSIDE,
TRANSCRIPTS_OUTSIDE,
WORKSPACE_RECORDS_HINT)
from ms_agent.utils.workspace_context import resolve_workspace_root

try:
workspace_root = Path(resolve_workspace_root(self.config))
except Exception: # noqa: BLE001 - never break prompt assembly
return ''
if not (workspace_root / 'sessions').is_dir():
return ''

# The directory the log is actually writing to, not the agent's tag:
# naming a path that does not exist is worse than naming none, since
# the model will go looking for it.
session_dir = None
log = getattr(self, 'session_log', None)
directory = getattr(log, 'directory', None)
if directory is not None:
session_dir = Path(directory).name
if not session_dir:
session_dir = getattr(self.runtime, 'session_id', None)

try:
home = str(global_home())
except Exception: # noqa: BLE001
home = '~/.ms_agent'
hint = WORKSPACE_INTERNALS_HINT.format(
session_line=(f' This conversation is `sessions/{session_dir}/`.'
if session_dir else ''),
home=home)
return hint

directory = getattr(
getattr(self, 'session_log', None), 'directory', None)
records_inside = None
if directory is not None:
try:
Path(directory).relative_to(workspace_root)
records_inside = True
except ValueError:
records_inside = False
if records_inside is None:
# No session log to consult (bare SDK / tests): fall back to the
# managed layout's signature.
if not (workspace_root / 'sessions').is_dir():
return ''
records_inside = True

if records_inside:
# The directory the log is actually writing to, not the agent's
# tag: naming a path that does not exist is worse than naming
# none, since the model will go looking for it.
session_dir = Path(directory).name if directory else None
if not session_dir:
session_dir = getattr(self.runtime, 'session_id', None)
transcripts_where = TRANSCRIPTS_INSIDE.format(session_line=(
f' This conversation is `sessions/{session_dir}/`.'
if session_dir else ''))
else:
transcripts_where = TRANSCRIPTS_OUTSIDE.format(
session_dir=str(Path(directory)))

return WORKSPACE_RECORDS_HINT.format(
transcripts_where=transcripts_where, home=home)

def _check_skill_tool_dependencies(self):
"""Warn if skills are enabled but essential tools are missing."""
Expand Down
106 changes: 60 additions & 46 deletions ms_agent/prompting/builtin.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
#: Bump when a template below changes materially. The workspace sidecar
#: records the version + sha256 written, so untouched files upgrade silently
#: while user-edited files are left alone (see workspace_files.py).
TEMPLATE_VERSION = 1
TEMPLATE_VERSION = 2

BASE_AGENT_PROMPT = """\
You are MS-Agent, a general-purpose assistant. You help with everyday work of
Expand All @@ -50,6 +50,10 @@
the machine: sending, publishing, deleting, paying, or overwriting user
files.
- The user's data is private. Never move it somewhere the user didn't intend.
- Credentials are off-limits unless the task genuinely requires them: private
keys, `.env` values, tokens, and password or cookie stores. When one is
truly needed, read the minimum and never repeat a secret's value into a
reply, file, log, or command line.
- Never bypass permission or approval mechanisms, even when asked to hurry.
"""

Expand All @@ -59,9 +63,9 @@
about: Personality and working attitude. Edit freely — this file is yours.
---

# Who You Are
## Who You Are

## Temperament
### Temperament
- **Direct.** Skip filler openers like "Great question!" — give the answer or
start the work.
- **Has judgment.** You may disagree and prefer things, with reasons. Don't
Expand All @@ -71,13 +75,13 @@
- **Plain words.** Lead with the conclusion, then the detail. Avoid jargon
walls.

## With your user
### With your user
- You work for a real person on real tasks, not a demo audience. Assume
competence; don't oversell or coddle.
- Unsure means saying so. Never paper over a gap with a confident tone.
- You are a guest. Their files, schedule, and accounts belong to them.

## Boundaries
### Boundaries
- Private things stay private.
- Outward actions (sending, publishing, deleting) get confirmed first.
"""
Expand Down Expand Up @@ -167,53 +171,63 @@
#: mechanism: without it, models plausibly (and wrongly) tell users their
#: system prompt is a session-start snapshot that cannot pick up file edits.
LIVE_FILES_HINT = """\
The persona, instructions and profile above come from workspace files \
The persona, instructions and profile above come from files \
(SOUL.md, AGENTS.md, PROFILE.md) that stay live during the conversation: \
edits apply from the next round, and this system prompt always shows the \
current file content. When files change mid-conversation, a \
<system-reminder> at the start of a user turn lists which ones changed. \
The ~/.ms_agent/... source labels are logical names — on this machine those \
files actually live in {home}; project AGENTS.md files live in the project \
directory."""

#: Injected when the framework keeps its own records INSIDE the working
#: directory, which is the layout of a managed project.
#:
#: Without it the agent has no way to tell its own bookkeeping apart from the
#: user's material, and the confusion is not hypothetical: searching the
#: workspace for a phrase finds that phrase in the transcript of the very
#: request being served, because the prompt was written there moments earlier.
#: Every hit is real, every hit is worthless, and the model has no reason to
#: suspect it. Naming the directories, and saying what changing them does, is
#: cheaper and less brittle than hiding them — hidden, they would also be
#: unavailable when the user genuinely asks about history or configuration.
WORKSPACE_INTERNALS_HINT = """\
## Framework files in your working directory

Two things under your working directory are maintained by the framework rather \
than written by the user:

- `sessions/` — a full transcript of every conversation in this project, \
including the user's messages verbatim.{session_line}
- `.ms_agent/` — this project's state: `memory/` (what is remembered across \
files actually live in {home}; a project's own AGENTS.md lives in its \
`.ms_agent/`."""

#: Where the framework keeps this project's records — ONE description for
#: both layouts. A managed project's working directory doubles as its records
#: directory; a mounted project keeps records in the data directory. Either
#: way the agent needs the same three facts: where transcripts are, what
#: ``.ms_agent/`` is, and that a search reaching those records matches its own
#: echo (the confusion is not hypothetical: the request being served is
#: already on disk when the search runs). Contents are described by example,
#: not enumerated — the exact file set varies by configuration and version,
#: and the model can list the directory when it matters.
WORKSPACE_RECORDS_HINT = """\
## Your workspace and the framework's records

The framework keeps records for this project in two places:

- **Conversation transcripts** — {transcripts_where} Every conversation is \
recorded verbatim, including the user's messages.
- **`.ms_agent/` under your working directory** — project state and \
per-project configuration: for example `memory/` (what is remembered across \
conversations), `snapshots/` (a git repository of previous workspace \
versions), `permission_memory.json` (approvals the user chose to keep), \
`web_search/` (cached search results), `mcp.json` and `project.json`.

Settings that apply to every project live separately, in {home} — the location \
is configurable, so a machine may have several and this conversation is using \
that one.

When you search the workspace, matches inside those two directories are the \
framework's record of this and earlier conversations, not the user's content. \
Anything you were just asked is already written to `sessions/`, so searching \
for a phrase from the request will match your own transcript. Exclude them \
unless the user is asking about history or configuration, and never cite such \
a match as if it were something you found in their material.

You may read these files, and edit them when asked. Be aware that editing \
`memory/` or `permission_memory.json` changes how later conversations behave, \
and that `snapshots/` is what makes reverting possible."""
versions), `permission_memory.json` (approvals the user chose to keep), and \
`mcp.json` / `skills/` / `AGENTS.md` when this project configures them. The \
exact contents vary — list the directory when you need to know. Everything \
else in the working directory is the user's own material.

Because transcripts contain what you were just asked, any search that \
reaches them — in the workspace or anywhere else on this machine — will \
match your own conversation. Treat such matches as the framework's records, \
not as something found in the user's material, and leave them out of \
results unless the user is asking about history or configuration.

You may read all of these records, and edit them when asked. Editing \
`memory/` or `permission_memory.json` changes how later conversations \
behave; `snapshots/` is what makes reverting possible.

Settings that apply to every project live in {home}."""

#: ``transcripts_where`` for the layout whose working directory doubles as
#: the records directory.
TRANSCRIPTS_INSIDE = ("in `sessions/` at the root of your working "
"directory.{session_line}")

#: ``transcripts_where`` for a mounted project: records live in the data
#: directory, and a `sessions/` folder in the workspace — if there is one —
#: belongs to the user.
TRANSCRIPTS_OUTSIDE = (
'outside the working directory: this conversation is recorded at '
'`{session_dir}`, and the project\'s other sessions sit beside it. Any '
'`sessions/` directory inside the working directory is the user\'s own.')

#: Filename -> template registry used by workspace_files.ensure logic.
HOME_FILE_TEMPLATES = {
Expand Down
4 changes: 2 additions & 2 deletions ms_agent/skill/prompt_injector.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
class SkillPromptInjector:
"""Builds the skill section to inject into the system prompt."""

SKILL_SECTION_HEADER = """# Available Skills
SKILL_SECTION_HEADER = """## Available Skills

You have access to specialized skills that extend your capabilities.
Each skill is a set of instructions and resources for handling specific tasks.
Expand All @@ -21,7 +21,7 @@ class SkillPromptInjector:
"""

ALWAYS_SKILLS_HEADER = (
'# Active Skills\n\n'
'## Active Skills\n\n'
'The following skills are always active. Follow their instructions.\n')

DISCOVERY_HINT = (
Expand Down
2 changes: 1 addition & 1 deletion ms_agent/tools/code/local_code_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,7 +563,7 @@ async def _get_tools_inner(self) -> Dict[str, Any]:
'temp directory); reading credential files and running '
'code inline (python -c, heredocs) may require '
'approval. Large output is spilled to '
'.ms_agent_artifacts and the result says where. Use '
'.ms_agent/artifacts and the result says where. Use '
'run_in_background=true for a long command: it returns '
'a task_id immediately.'),
parameters={
Expand Down
79 changes: 76 additions & 3 deletions ms_agent/tools/mcp_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import copy
import os
import re
import shutil
from contextlib import AsyncExitStack, suppress
from datetime import timedelta
from mcp import ClientSession, ListToolsResult, StdioServerParameters
Expand Down Expand Up @@ -61,6 +62,63 @@ def _int_env(name: str, default: int) -> int:
DEFAULT_STREAMABLE_HTTP_TIMEOUT = timedelta(seconds=30)
DEFAULT_STREAMABLE_HTTP_SSE_READ_TIMEOUT = timedelta(seconds=60 * 5)

#: Variables a stdio server's child process inherits from this one. The MCP
#: SDK's default child environment is deliberately tiny, which strips proxy,
#: TLS and package-index settings — a cold ``uvx <package>`` then downloads
#: without the proxy the machine needs and runs into the startup timeout.
#: Identity, toolchain and network settings pass through; credentials do not.
#: A server's configured ``env`` is applied on top and wins per key.
_STDIO_ENV_PASSTHROUGH = (
'PATH', 'HOME', 'USER', 'LOGNAME', 'SHELL', 'TMPDIR', 'TERM', 'TZ',
'LANG', 'LC_ALL', 'LC_CTYPE',
'SSL_CERT_FILE', 'SSL_CERT_DIR', 'REQUESTS_CA_BUNDLE',
# ALL_PROXY is deliberately absent: a socks5:// value makes any child
# whose httpx lacks the socksio extra fail on its FIRST request, and the
# child's venv is not ours to fix. The scheme-specific variables cover
# the download-acceleration need without that trap.
'HTTP_PROXY', 'HTTPS_PROXY', 'NO_PROXY',
'http_proxy', 'https_proxy', 'no_proxy',
'UV_INDEX_URL', 'UV_DEFAULT_INDEX', 'PIP_INDEX_URL',
'npm_config_registry',
)

#: Where a bare stdio command is looked for when PATH does not know it. A
#: backend launched by an IDE or launchd runs with a minimal PATH while the
#: user's ``uvx``/``npx`` lives in one of these.
_STDIO_EXTRA_BIN_DIRS = (
os.path.expanduser('~/.local/bin'),
'/opt/homebrew/bin',
'/usr/local/bin',
)


def stdio_child_env(config_env: Optional[dict]) -> dict:
env = {
key: os.environ[key]
for key in _STDIO_ENV_PASSTHROUGH if os.environ.get(key)
}
for key, value in (config_env or {}).items():
env[str(key)] = str(value)
return env


def resolve_stdio_command(command: str) -> str:
"""Absolute path for *command*, searching PATH then the usual user dirs."""
if os.path.sep in command:
return command
found = shutil.which(command)
if found:
return found
for base in _STDIO_EXTRA_BIN_DIRS:
candidate = os.path.join(base, command)
if os.path.isfile(candidate) and os.access(candidate, os.X_OK):
return candidate
searched = ':'.join([os.environ.get('PATH', '')] +
list(_STDIO_EXTRA_BIN_DIRS))
raise FileNotFoundError(
f"stdio MCP command '{command}' was not found on this machine. "
f'Searched: {searched}')


_PYDANTIC_DOC_LINK = re.compile(r'\s*For further information visit \S+')
_PYDANTIC_FIELD_LINE = re.compile(r'^(\S+?)(?:\.\w+)?\n\s+(.+?)\s*\[type=',
Expand Down Expand Up @@ -130,8 +188,18 @@ def __init__(

async def call_tool(self, server_name: str, tool_name: str,
tool_args: dict):
response = await self.sessions[server_name].call_tool(
tool_name, tool_args)
session = self.sessions.get(server_name)
if session is None:
# The server's tools stay in the tool index after its connection
# dies (a failed call tears the session down), so the model can
# still address it — and used to get a bare KeyError. Name what
# actually happened and what to do instead.
raise RuntimeError(
f"MCP server '{server_name}' is not connected (it failed or "
'was disconnected earlier in this conversation). Its tools '
'are unavailable for now — do not retry this call; use a '
'different approach or tell the user the server is down.')
response = await session.call_tool(tool_name, tool_args)

texts = []
resources = []
Expand Down Expand Up @@ -369,10 +437,15 @@ async def _open_session(self, stack: AsyncExitStack, server_name: str,
if not args:
raise ValueError(
"'args' parameter is required for stdio connection")
if os.name == 'nt':
child_env = kwargs.get('env')
else:
command = resolve_stdio_command(command)
child_env = stdio_child_env(kwargs.get('env'))
server_params = StdioServerParameters(
command=command,
args=args,
env=kwargs.get('env'),
env=child_env,
encoding=kwargs.get('encoding', DEFAULT_ENCODING),
encoding_error_handler=kwargs.get(
'encoding_error_handler', DEFAULT_ENCODING_ERROR_HANDLER),
Expand Down
2 changes: 1 addition & 1 deletion ms_agent/utils/artifact_manager.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
"""Spill large tool outputs to disk under output_dir/.ms_agent_artifacts/."""
"""Spill large tool outputs to disk under output_dir/.ms_agent/artifacts/."""

from __future__ import annotations

Expand Down
Loading
Loading