Assemble the cold-start system prompt for the headless+TUI cutover. Returns the FULL system prompt as a **block list** (``list[dict]``) so the ``query()`` loop still engages prompt caching: the canonical base sections (``build_full_system_prompt_blocks`` — intro / # Doing tasks / # Exec
(
style_prompt: str,
tool_context: ToolContext,
*,
provider: Any | None = None,
mcp_servers: list[Any] | None = None,
query_source: str = "main",
)
| 174 | |
| 175 | |
| 176 | def build_effective_system_prompt( |
| 177 | style_prompt: str, |
| 178 | tool_context: ToolContext, |
| 179 | *, |
| 180 | provider: Any | None = None, |
| 181 | mcp_servers: list[Any] | None = None, |
| 182 | query_source: str = "main", |
| 183 | ) -> list[dict[str, Any]]: |
| 184 | """Assemble the cold-start system prompt for the headless+TUI cutover. |
| 185 | |
| 186 | Returns the FULL system prompt as a **block list** (``list[dict]``) so the |
| 187 | ``query()`` loop still engages prompt caching: the canonical base sections |
| 188 | (``build_full_system_prompt_blocks`` — intro / # Doing tasks / # Executing |
| 189 | actions / # Using your tools / # Tone / output-efficiency / env / memory / |
| 190 | skills / …) with the resolved output-style prompt **appended**, then the |
| 191 | existing workspace + git + CLAUDE.md context preserved as a trailing |
| 192 | (uncached) block. |
| 193 | |
| 194 | Why this exists: the TUI (``tui/agent_bridge.py``) and headless |
| 195 | (``entrypoints/headless.py``) cutover routes through ``query()``, which |
| 196 | passes ``params.system_prompt`` **verbatim** to the model — it has no base |
| 197 | build of its own. The engine/REPL path (``engine.py:124-188``) does the |
| 198 | canonical build when ``system_prompt`` is unset, but the cutover pre-sets |
| 199 | it, so before this fix the live TUI/headless agent received **no base |
| 200 | instructions** at all (only the style line + context). This helper restores |
| 201 | them, mirroring the engine's ``build_full_system_prompt_blocks`` + |
| 202 | ``append_system_prompt`` shape. |
| 203 | |
| 204 | CLAUDE.md note: ``build_full_system_prompt_blocks``' memory section is |
| 205 | *auto-memory* (``MEMORY.md`` via ``load_memory_prompt``), **not** CLAUDE.md. |
| 206 | On the engine path CLAUDE.md is injected into the *messages* via |
| 207 | ``prepend_user_context``; the cutover does not do that, so we keep |
| 208 | ``build_context_prompt`` (which emits ``## Project Instructions``) to |
| 209 | preserve CLAUDE.md — option (b) in |
| 210 | ``my-docs/get-parity-by-folder/live-base-system-prompt-gap-analysis.md``. |
| 211 | This overlaps the base ``# Environment`` section on CWD/date (a benign, |
| 212 | documented duplication). |
| 213 | |
| 214 | ``tools``/``tool_registry`` are deliberately NOT passed to |
| 215 | ``build_full_system_prompt_blocks`` (matching ``engine.py:167``): tool |
| 216 | schemas reach the model via the API ``tools=`` param, so emitting a prose |
| 217 | tool-docs section here would double-send them. ``provider``/``mcp_servers`` |
| 218 | feed only the global cache-scope gate; ``None`` is safe (disables the |
| 219 | cross-user global scope, which TUI/headless should not use). |
| 220 | """ |
| 221 | # Local imports — context_system is a heavier dep; only the cutover |
| 222 | # callers need it, no need to drag it into agent_loop_compat's import time. |
| 223 | from ..context_system import build_context_prompt |
| 224 | from ..context_system.prompt_assembly import build_full_system_prompt_blocks |
| 225 | |
| 226 | cwd = str(tool_context.cwd or tool_context.workspace_root) |
| 227 | |
| 228 | # Skills listing (best-effort; mirrors engine.py:183). |
| 229 | try: |
| 230 | from ..command_system import get_skill_tool_commands |
| 231 | skills = get_skill_tool_commands(cwd) |
| 232 | except Exception: |
| 233 | skills = None |