Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ test/
# Distribution / packaging
.Python
build/
!ms_agent/agent/templates/build/
!ms_agent/agent/templates/build/**
develop-eggs/
dist/
downloads/
Expand Down
3 changes: 3 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ include requirements.txt
recursive-include requirements *.txt
recursive-include ms_agent/ *.yaml

# Include built-in agent templates (yaml + prompt files)
recursive-include ms_agent/agent/templates *

# Include projects
recursive-include projects *

Expand Down
3 changes: 3 additions & 0 deletions ms_agent/agent/loader.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ def build(cls,
**kwargs) -> Agent:
agent_config: Optional[DictConfig] = None
if config_dir_or_id is not None:
from ms_agent.agent.templates.registry import \
resolve_template_source
config_dir_or_id = resolve_template_source(config_dir_or_id)
if not os.path.exists(config_dir_or_id):
from modelscope import snapshot_download
config_dir_or_id = snapshot_download(config_dir_or_id)
Expand Down
24 changes: 24 additions & 0 deletions ms_agent/agent/templates/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright (c) Alibaba, Inc. and its affiliates.
"""Built-in template agents (general / plan / explore / build / research).

Importing this package also registers the built-in harness callbacks
(``stop_gate``, ``round_reminder``, ``model_quality_check``) into
``ms_agent.callbacks.callbacks_mapping`` so templates can reference them in
``callbacks:`` without ``trust_remote_code``. The import is best-effort: a
failure here must never break config loading.
"""
from .registry import (get_when_to_use, list_templates, load_manifest,
resolve_template_dir, resolve_template_source)

try: # best-effort harness registration; see templates/harness/__init__.py
from . import harness # noqa: F401
except Exception: # pragma: no cover - harness is optional
pass

__all__ = [
'resolve_template_source',
'resolve_template_dir',
'load_manifest',
'list_templates',
'get_when_to_use',
]
43 changes: 43 additions & 0 deletions ms_agent/agent/templates/build/agent.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Built-in template: build (model_tier: strong)
# Coding sub-agent: implement, edit, debug, verify.
llm:
service: openai
model: qwen3.7-plus # strong tier; bump to qwen3-max if desired
openai_api_key: <OPENAI_API_KEY>
openai_base_url: <OPENAI_BASE_URL>

generation_config:
stream: true

prompt:
base: worker # shared sub-agent base prompt (prompts/base/worker.md)
system: | # specialization only
You are a coding agent. For each task:
1) Analyze the requirements first, then implement.
2) Prefer minimal, correct edits that match the surrounding code style.
3) Run code / tests to verify your changes; read errors, diagnose, and fix
them -- don't give up after one attempt.
4) Stay in scope: don't refactor unrelated code. Never run destructive
commands without explicit instruction.
5) Report what you changed and how you verified it.

# No interactive input_callback: build is a focused, run-once / sub-agent
# template (input_callback would block on stdin when invoked as a sub-agent).
callbacks:
- state_inject
- loop_guard # break invalid tool-call loops
- todo_gate # don't stop with unfinished plan items

tools:
file_system:
mcp: false
include: [read_file, write_file, edit_file, grep, glob]
code_executor:
mcp: false
implementation: python_env
todo_list:
mcp: false
auto_render_md: true
include: [todo_write, todo_read]

max_chat_round: 45
48 changes: 48 additions & 0 deletions ms_agent/agent/templates/compose_prompt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Copyright (c) Alibaba, Inc. and its affiliates.
"""Compose a layered system prompt: a shared BASE + a per-template SPECIALIZATION.

A template declares::

prompt:
base: general # general | worker | none (default: none)
system: | # specialization only
<template-specific role / boundaries / workflow>

``compose_system_prompt`` reads ``prompts/base/<base>.md`` and prepends it to
``prompt.system``. Non-template configs (no ``prompt.base``) are untouched.

Environment placeholders in the base (``<current_date>`` / ``<cwd>`` / ``<os>``)
are filled at run time by ``StateInjectCallback`` (kept out of load-time so the
prompt text stays stable for caching across processes).
"""
from __future__ import annotations

from pathlib import Path

_BASE_DIR = Path(__file__).resolve().parent / 'prompts' / 'base'


def _base_path(name: str) -> Path:
return _BASE_DIR / f'{name}.md'


def compose_system_prompt(config):
"""Prepend the selected base prompt to ``config.prompt.system``.

Returns ``config`` unchanged when there is no ``prompt.base`` (or it is
``none`` / missing on disk). Best-effort: never raises.
"""
prompt = getattr(config, 'prompt', None)
if prompt is None:
return config
base_name = getattr(prompt, 'base', None)
if not base_name or str(base_name).lower() == 'none':
return config
path = _base_path(str(base_name))
if not path.is_file():
return config
base_text = path.read_text(encoding='utf-8').rstrip()
spec = getattr(prompt, 'system', '') or ''
spec = spec.strip()
config.prompt.system = base_text + ('\n\n' + spec if spec else '')
return config
39 changes: 39 additions & 0 deletions ms_agent/agent/templates/explore/agent.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Built-in template: explore (model_tier: fast)
# Fast, read-only exploration sub-agent.
llm:
service: openai
model: qwen3.7-plus # fast tier
openai_api_key: <OPENAI_API_KEY>
openai_base_url: <OPENAI_BASE_URL>

generation_config:
stream: true

prompt:
base: worker # shared sub-agent base prompt (prompts/base/worker.md)
system: | # specialization only
You are a read-only exploration specialist: locate files, search code /
content, and answer questions about the codebase or materials.
- Use grep/glob for patterns, read_file for known paths, web search for the web.
- Return absolute file paths and concise, evidence-backed findings.
- Adapt depth to the requested thoroughness (quick / medium / very thorough).
You MUST NOT create or modify any files, or run anything that changes system
state. If asked to change something, report what should change instead.

# No interactive input_callback: explore is a focused, run-once / sub-agent
# template (input_callback would block on stdin when invoked as a sub-agent).
callbacks:
- state_inject
- loop_guard # break invalid tool-call loops

tools:
file_system:
mcp: false
include: [read_file, grep, glob]
web_search:
mcp: false
engine: exa
fetch_content: true
max_results: 5

max_chat_round: 30
52 changes: 52 additions & 0 deletions ms_agent/agent/templates/general/agent.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Built-in template: general (model_tier: strong)
# Default entry point: full analyze -> plan -> execute; can delegate to sub-agents.
llm:
service: openai
model: qwen3.7-plus # strong tier; bump to qwen3-max if desired
openai_api_key: <OPENAI_API_KEY>
openai_base_url: <OPENAI_BASE_URL>

generation_config:
stream: true

prompt:
base: general # shared base prompt (prompts/base/general.md)
system: | # specialization only
Delegate a subtask to a sub-agent when it is genuinely better handled in
isolation (do not delegate trivial work):
- `explore` : read-only search / locate across many files or sources,
- `build` : a focused coding / debugging subtask,
- `research` : an in-depth, citation-backed research subtask.
For non-trivial work, sketch a short plan with the todo tools before executing.

callbacks:
- input_callback
- state_inject
- loop_guard # break invalid tool-call loops
- subagent_limit # cap parallel sub-agent delegations per turn

subagent_limit:
max_parallel: 4

tools:
file_system:
mcp: false
include: [read_file, write_file, edit_file, grep, glob]
code_executor:
mcp: false
implementation: python_env
web_search:
mcp: false
engine: exa
fetch_content: true
max_results: 5
todo_list:
mcp: false
auto_render_md: true
include: [todo_write, todo_read]

# Sub-agent delegation. Expanded into tools.agent_tools.definitions at load time
# by expand_subagents(); descriptions come from templates/registry.yaml.
subagents: [explore, build, research]

max_chat_round: 9999
52 changes: 52 additions & 0 deletions ms_agent/agent/templates/harness/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Copyright (c) Alibaba, Inc. and its affiliates.
"""Reusable harness callbacks for template agents.

These are generalized from deep_research v2's private callbacks so that any
template (or user config) can opt into them via ``callbacks:``.

Importing this module registers the callbacks into
``ms_agent.callbacks.callbacks_mapping`` (using ``setdefault`` so it never
clobbers existing entries), which lets templates reference
``callbacks: [round_reminder, stop_gate]`` WITHOUT ``trust_remote_code``.
"""
from .loop_guard import LoopGuardCallback
from .plan_check import PlanCheckCallback
from .round_reminder import RoundReminderCallback
from .state_inject import StateInjectCallback
from .stop_gate import StopGateCallback
from .subagent_limit import SubagentLimitCallback
from .todo_gate import TodoGateCallback

_HARNESS_CALLBACKS = {
'round_reminder': RoundReminderCallback,
'stop_gate': StopGateCallback,
'state_inject': StateInjectCallback,
'loop_guard': LoopGuardCallback,
'todo_gate': TodoGateCallback,
'plan_check': PlanCheckCallback,
'subagent_limit': SubagentLimitCallback,
}


def register_harness_callbacks() -> None:
"""Idempotently register harness callbacks into the global mapping."""
try:
from ms_agent.callbacks import callbacks_mapping
for name, cls in _HARNESS_CALLBACKS.items():
callbacks_mapping.setdefault(name, cls)
except Exception: # pragma: no cover - registration is best-effort
pass


register_harness_callbacks()

__all__ = [
'RoundReminderCallback',
'StopGateCallback',
'StateInjectCallback',
'LoopGuardCallback',
'TodoGateCallback',
'PlanCheckCallback',
'SubagentLimitCallback',
'register_harness_callbacks',
]
Loading
Loading