From 5f28a49bc1cb0348dd6cb6210ae3f1fafaa13bf4 Mon Sep 17 00:00:00 2001 From: worldmozara Date: Tue, 8 Sep 2026 13:09:23 +0800 Subject: [PATCH] config: add [agent] mcp_instructions and variadic system compose --- src/plyngent/config/models.py | 21 ++++++++++----------- tests/test_config/test_agent_section.py | 17 +++++++++++++++++ 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/src/plyngent/config/models.py b/src/plyngent/config/models.py index 8457e30..ffb8d83 100644 --- a/src/plyngent/config/models.py +++ b/src/plyngent/config/models.py @@ -70,19 +70,15 @@ call `todo_clear` (or pop finished TOP groups) so no hygiene noise is left behin """ -def compose_agent_system_content( - system_prompt: str, - tool_directives: str, -) -> str | None: - """Join persona + tool playbook into one system body, or ``None`` if both empty. +def compose_agent_system_content(*parts: str) -> str | None: + """Join persona + tool playbook + extra blocks into one system body. - Non-empty parts are stripped and joined with a blank line. Either field may - be ``""`` to disable that part while keeping the other. + Returns ``None`` when every part is empty. Non-empty parts are stripped and + joined with a blank line. Any part may be ``""`` to disable that block + (e.g. ``system_prompt = ""`` keeps the tool playbook). """ - parts = [part.strip() for part in (system_prompt, tool_directives) if part and part.strip()] - if not parts: - return None - return "\n\n".join(parts) + joined = "\n\n".join(part.strip() for part in parts if part and part.strip()) + return joined or None class DatabaseConfig(Struct, omit_defaults=True): @@ -106,6 +102,9 @@ class AgentConfig(Struct, omit_defaults=True): system_prompt: str = DEFAULT_SYSTEM_PROMPT # Tool how-to (omit → DEFAULT_TOOL_DIRECTIVES; "" disables playbook only). tool_directives: str = DEFAULT_TOOL_DIRECTIVES + # Fold connected MCP servers' initialize ``instructions`` (usage guidance) + # into the agent system prompt when tools are on. false = ignore server text. + mcp_instructions: bool = True max_tool_result_chars: int = 32_000 parallel_tools: bool = True confirm_destructive: bool = True diff --git a/tests/test_config/test_agent_section.py b/tests/test_config/test_agent_section.py index b666c11..1e630f5 100644 --- a/tests/test_config/test_agent_section.py +++ b/tests/test_config/test_agent_section.py @@ -19,6 +19,7 @@ def test_agent_section_defaults(tmp_path: Path) -> None: store = load(path) assert store.agent_config.system_prompt == DEFAULT_SYSTEM_PROMPT assert store.agent_config.tool_directives == DEFAULT_TOOL_DIRECTIVES + assert store.agent_config.mcp_instructions is True assert "professional coding agent" in store.agent_config.system_prompt assert "### Workspace" in store.agent_config.tool_directives assert store.agent_config.max_tool_result_chars == 32_000 @@ -64,6 +65,9 @@ def test_compose_empty_combinations() -> None: assert compose_agent_system_content("Only persona", "") == "Only persona" assert compose_agent_system_content("", "Only tools") == "Only tools" assert compose_agent_system_content("Persona", "Tools") == "Persona\n\nTools" + assert compose_agent_system_content("", "", "") is None + assert compose_agent_system_content("Persona", "Tools", "Extra") == "Persona\n\nTools\n\nExtra" + assert compose_agent_system_content("Persona", "", "Extra") == "Persona\n\nExtra" def test_agent_section_parse(tmp_path: Path) -> None: @@ -158,6 +162,19 @@ tool_directives = "" ) +def test_agent_mcp_instructions_flag(tmp_path: Path) -> None: + path = tmp_path / "c.toml" + _ = path.write_text( + """ +[agent] +mcp_instructions = false +""", + encoding="utf-8", + ) + store = load(path) + assert store.agent_config.mcp_instructions is False + + def test_plugins_section_parse(tmp_path: Path) -> None: path = tmp_path / "c.toml" _ = path.write_text(