core/config: default system prompt for coding agent tools

This commit is contained in:
2026-07-20 18:21:27 +08:00
parent 767578723e
commit 6d74d31cea
7 changed files with 69 additions and 7 deletions
+1 -1
View File
@@ -72,7 +72,7 @@ Async SQLAlchemy + aiosqlite. `MemoryStore`: schema init (+ lightweight SQLite `
- **`/compact`**: soft-compact tool dumps → model summary (no tools) → **new** session seeded with summary message. Soft-compact keeps the last **12** tool results full-size by default (`DEFAULT_RECENT_TOOL_RESULTS`); older tool payloads shrink first. - **`/compact`**: soft-compact tool dumps → model summary (no tools) → **new** session seeded with summary message. Soft-compact keeps the last **12** tool results full-size by default (`DEFAULT_RECENT_TOOL_RESULTS`); older tool payloads shrink first.
- Events: text_delta, **reasoning_delta**, assistant_message, tool_call/result, max_rounds, **error** (`retryable`/`source`), **cancelled** (`reason`), **usage** (`TokenUsage`). - Events: text_delta, **reasoning_delta**, assistant_message, tool_call/result, max_rounds, **error** (`retryable`/`source`), **cancelled** (`reason`), **usage** (`TokenUsage`).
- Usage: API `usage` from completions (stream with `include_usage`); **char≈token fallback** (~4 chars/token) when omitted; **context size** = last request ``prompt_tokens`` (API preferred); `last_turn_usage` / `session_usage` are **billed sums** (tool rounds re-send history); CLI end-of-turn + `/status`. - Usage: API `usage` from completions (stream with `include_usage`); **char≈token fallback** (~4 chars/token) when omitted; **context size** = last request ``prompt_tokens`` (API preferred); `last_turn_usage` / `session_usage` are **billed sums** (tool rounds re-send history); CLI end-of-turn + `/status`.
- Config ``[agent]``: `system_prompt`, `max_tool_result_chars`, `parallel_tools`, `confirm_destructive`, `path_denylist`, `max_context_tokens` (default 200k est. tokens). - Config ``[agent]``: `system_prompt` (default: built-in coding-agent guide `DEFAULT_SYSTEM_PROMPT`; `""` disables), `max_tool_result_chars`, `parallel_tools`, `confirm_destructive`, `path_denylist`, `max_context_tokens` (default 200k est. tokens).
### Tools (`tools/`) ### Tools (`tools/`)
+4 -1
View File
@@ -136,7 +136,10 @@ access_key_or_token = "sk-..."
"gpt-4o-mini" = { text = true } "gpt-4o-mini" = { text = true }
[agent] [agent]
system_prompt = "You are a careful coding assistant." # system_prompt defaults to a built-in coding-agent guide (omit to use it).
# system_prompt = "" # disable
# Or multi-line override (prefer '''...'''):
# system_prompt = '''Your custom prompt...'''
confirm_destructive = true confirm_destructive = true
max_context_tokens = 200000 max_context_tokens = 200000
``` ```
+6 -1
View File
@@ -11,7 +11,12 @@
# # url = ":memory:" # # url = ":memory:"
[agent] [agent]
system_prompt = "You are a careful coding assistant. Prefer small, verified edits." # system_prompt defaults to the built-in coding-agent guide when omitted.
# Override with a multi-line literal (prefer ''' so nested " is fine):
# system_prompt = '''
# Your custom system prompt...
# '''
# system_prompt = "" # disable system prompt entirely
max_tool_result_chars = 32000 max_tool_result_chars = 32000
parallel_tools = true parallel_tools = true
confirm_destructive = true confirm_destructive = true
+2 -1
View File
@@ -29,7 +29,8 @@ _MINIMAL_CONFIG = """\
# # url = ":memory:" # # url = ":memory:"
# [agent] # [agent]
# system_prompt = "You are a careful coding assistant." # # system_prompt defaults to the built-in coding-agent guide when omitted.
# # system_prompt = "" # disable; or multi-line '''...''' to override
# max_tool_result_chars = 32000 # max_tool_result_chars = 32000
# parallel_tools = true # parallel_tools = true
# confirm_destructive = true # confirm_destructive = true
+1
View File
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING
import tomlkit import tomlkit
from tomlkit.exceptions import TOMLKitError from tomlkit.exceptions import TOMLKitError
from .models import DEFAULT_SYSTEM_PROMPT as DEFAULT_SYSTEM_PROMPT
from .models import AgentConfig as AgentConfig from .models import AgentConfig as AgentConfig
from .models import AnthropicProvider as AnthropicProvider from .models import AnthropicProvider as AnthropicProvider
from .models import DatabaseConfig as DatabaseConfig from .models import DatabaseConfig as DatabaseConfig
+39 -1
View File
@@ -2,6 +2,44 @@ from typing import Any
from msgspec import Struct, field from msgspec import Struct, field
# Built-in agent system prompt when ``[agent].system_prompt`` is omitted.
# Set ``system_prompt = ""`` in TOML to disable. Override with a multi-line
# TOML literal (prefer '''...''' so nested " quotes are fine).
DEFAULT_SYSTEM_PROMPT = """\
You are a professional coding agent in a workspace-bound tool environment.
### Workspace
- Explore with `tree`/`listdir`/`glob_paths`/`grep_files`/`read_file` when unsure.
- Stay under the workspace (or a path from `new_temporary_workspace`). Respect path denylists.
### Files
- Prefer file tools over shell. `edit_replace` fails usually means a bad match — \
fix `old_string` or `max_replaces`; shell workarounds rarely help.
- `edit_replace` defaults to the first match; if remaining matches are reported, \
raise `max_replaces` or narrow `old_string`.
- Before `edit_lineno`, `read_file` with `with_lineno=true`.
### Commands
- Prefer `run_command` / `run_command_batch` (argv, no shell) over `bash -c` or similar.
- Several `run_command` calls in one step may run in parallel; use `run_command_batch` \
for ordered pipelines (`pipe_out` / `mix_stderr` as needed).
- Prefer `vcs_*` for status/diff/log/branch when enough.
### Humans & safety
- Prefer `ask_user_line` / `ask_user_choice` / `ask_user_form` over waiting for the next free-form turn.
- Overwrites, deletes, shells, and risky ops may require human confirm; hard denylists are not skipped by YOLO.
- Temporary scratch: `new_temporary_workspace`.
### PTY
- Use PTY for interactive/TUI, sudo, ssh, or live servers.
- Passwords and other secret input: only `ask_into_pty` (never echo secrets into `write_pty`).
- Control keys: `write_pty_keys`, not literal `write_pty` data.
### Todos
- Use the todo stack for multi-step work (LIFO groups: push related items, finish/update, \
pop the group). Open items mean unfinished work.
"""
class DatabaseConfig(Struct, omit_defaults=True): class DatabaseConfig(Struct, omit_defaults=True):
"""Database connection configuration. """Database connection configuration.
@@ -20,7 +58,7 @@ class DatabaseConfig(Struct, omit_defaults=True):
class AgentConfig(Struct, omit_defaults=True): class AgentConfig(Struct, omit_defaults=True):
"""Single-user agent profile defaults.""" """Single-user agent profile defaults."""
system_prompt: str = "" system_prompt: str = DEFAULT_SYSTEM_PROMPT
max_tool_result_chars: int = 32_000 max_tool_result_chars: int = 32_000
parallel_tools: bool = True parallel_tools: bool = True
confirm_destructive: bool = True confirm_destructive: bool = True
+16 -2
View File
@@ -2,7 +2,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from plyngent.config import load from plyngent.config import DEFAULT_SYSTEM_PROMPT, load
if TYPE_CHECKING: if TYPE_CHECKING:
from pathlib import Path from pathlib import Path
@@ -12,7 +12,8 @@ def test_agent_section_defaults(tmp_path: Path) -> None:
path = tmp_path / "c.toml" path = tmp_path / "c.toml"
_ = path.write_text("", encoding="utf-8") _ = path.write_text("", encoding="utf-8")
store = load(path) store = load(path)
assert store.agent_config.system_prompt == "" assert store.agent_config.system_prompt == DEFAULT_SYSTEM_PROMPT
assert "professional coding agent" in store.agent_config.system_prompt
assert store.agent_config.max_tool_result_chars == 32_000 assert store.agent_config.max_tool_result_chars == 32_000
assert store.agent_config.parallel_tools is True assert store.agent_config.parallel_tools is True
assert store.agent_config.confirm_destructive is True assert store.agent_config.confirm_destructive is True
@@ -41,3 +42,16 @@ max_context_tokens = 5000
assert store.agent_config.confirm_destructive is False assert store.agent_config.confirm_destructive is False
assert store.agent_config.path_denylist == ["/secrets/", ".ssh/"] assert store.agent_config.path_denylist == ["/secrets/", ".ssh/"]
assert store.agent_config.max_context_tokens == 5000 assert store.agent_config.max_context_tokens == 5000
def test_agent_system_prompt_empty_disables(tmp_path: Path) -> None:
path = tmp_path / "c.toml"
_ = path.write_text(
"""
[agent]
system_prompt = ""
""",
encoding="utf-8",
)
store = load(path)
assert store.agent_config.system_prompt == ""