# Copyright 2025 The EasyDeL/Calute Author @erfanzar (Erfan Zare Chavoshi).
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Runtime context assembly for Calute system prompts.
Builds rich system prompt sections that include:
- Runtime/environment context (date, platform, version)
- Workspace context (working directory, project info)
- Sandbox status
- Skills index
- Tooling summary
- Safety/guardrail reminders
- Bootstrap file injections from hooks
Supports :class:`~calute.runtime.profiles.PromptProfile` to control
verbosity (full, compact, minimal, none) for sub-agent delegation.
"""
from __future__ import annotations
import os
import platform
import typing as tp
from dataclasses import dataclass
from datetime import datetime
from .profiles import PromptProfile, PromptProfileConfig, get_profile_config
if tp.TYPE_CHECKING:
from ..extensions.hooks import HookRunner
from ..extensions.plugins import PluginRegistry
from ..extensions.skills import Skill, SkillRegistry
from ..security.sandbox import SandboxConfig
[docs]@dataclass
class RuntimeInfo:
"""Snapshot of runtime environment information.
Captures a point-in-time view of the host environment including
timestamps, platform details, Python and Calute versions, and the
current workspace directory. Used by :class:`PromptContextBuilder`
to populate runtime and workspace prompt sections.
Attributes:
timestamp: ISO 8601 formatted local timestamp with timezone offset.
timezone: Name of the local timezone (e.g. ``"UTC"``, ``"PST"``).
platform: Operating system name and release (e.g. ``"Darwin 23.4.0"``).
python_version: Python interpreter version string (e.g. ``"3.12.3"``).
calute_version: Installed Calute package version.
working_directory: Absolute path to the resolved workspace directory.
workspace_name: Base name of the workspace directory.
"""
timestamp: str = ""
timezone: str = ""
platform: str = ""
python_version: str = ""
calute_version: str = ""
working_directory: str = ""
workspace_name: str = ""
[docs] @classmethod
def capture(cls, workspace_root: str | None = None) -> RuntimeInfo:
"""Capture a snapshot of the current runtime environment.
Reads the local clock, platform details, Python version, and the
installed Calute version to produce a frozen :class:`RuntimeInfo`
instance.
Args:
workspace_root: Optional explicit workspace directory. When
``None``, falls back to the current working directory.
Returns:
A new :class:`RuntimeInfo` populated with current environment
data.
"""
from calute import __version__
now = datetime.now().astimezone()
cwd = os.path.abspath(workspace_root or os.getcwd())
return cls(
timestamp=now.isoformat(timespec="seconds"),
timezone=now.tzname() or "local",
platform=f"{platform.system()} {platform.release()}",
python_version=platform.python_version(),
calute_version=__version__,
working_directory=cwd,
workspace_name=os.path.basename(cwd),
)
[docs]@dataclass
class PromptContext:
"""Assembled context sections for system prompt enrichment.
Each field holds a pre-rendered string for one logical section of the
system prompt. Fields that are empty (``""``) indicate that the
corresponding section was disabled by the active
:class:`~calute.runtime.profiles.PromptProfileConfig`.
Attributes:
runtime_section: Platform, Python version, and Calute version block.
workspace_section: Working directory and project name block.
datetime_section: Current local date/time and timezone block.
reasoning_section: Profile name and reasoning guidance block.
sandbox_section: Sandbox mode and tool routing block.
skills_section: Index of all discovered skills from the registry.
enabled_skills_section: Full instruction text for skills that are
currently enabled for the agent.
tools_section: List of available tool names for this run.
guardrails_section: Active guardrail rules for safety enforcement.
bootstrap_section: Injected project/bootstrap content from hook
runners.
"""
runtime_section: str = ""
workspace_section: str = ""
datetime_section: str = ""
reasoning_section: str = ""
sandbox_section: str = ""
skills_section: str = ""
enabled_skills_section: str = ""
tools_section: str = ""
guardrails_section: str = ""
bootstrap_section: str = ""
def _resolve_profile_config(
profile: PromptProfile | PromptProfileConfig | str | None,
) -> PromptProfileConfig:
"""Normalise a profile argument into a concrete :class:`PromptProfileConfig`.
Accepts several input forms for ergonomic use across the codebase:
* ``None`` -- returns the default :data:`PromptProfile.FULL` config.
* A :class:`PromptProfile` enum member -- looked up via
:func:`get_profile_config`.
* A lowercase string (``"compact"``, ``"minimal"``, etc.) -- converted
to :class:`PromptProfile` then looked up.
* An existing :class:`PromptProfileConfig` -- returned as-is.
Args:
profile: The profile specification to resolve. May be ``None``,
a :class:`PromptProfile` enum value, a string name, or an
already-resolved :class:`PromptProfileConfig`.
Returns:
A fully resolved :class:`PromptProfileConfig` instance.
Raises:
ValueError: If *profile* is a string that does not match any
:class:`PromptProfile` member.
"""
if profile is None:
return get_profile_config(PromptProfile.FULL)
if isinstance(profile, PromptProfile):
return get_profile_config(profile)
if isinstance(profile, str):
return get_profile_config(PromptProfile(profile.strip().lower()))
return profile
[docs]class PromptContextBuilder:
"""Builds enriched prompt context from runtime state.
Orchestrates the construction of all system-prompt sections that
describe the runtime environment, available tools, skills, sandbox
configuration, guardrails, and workspace context. The builder
supports multiple verbosity levels via
:class:`~calute.runtime.profiles.PromptProfile` and can produce
agent-specific overrides.
Attributes:
skill_registry: Registry of discovered skills.
plugin_registry: Registry of discovered plugins.
hook_runner: Hook runner for bootstrap file injection.
sandbox_config: Default sandbox configuration.
guardrails: Default list of guardrail rule strings.
default_profile_config: Resolved default prompt profile config.
workspace_root: Optional explicit workspace directory path.
Example:
>>> builder = PromptContextBuilder(skill_registry=my_registry)
>>> ctx = builder.build(agent_id="coder")
>>> print(ctx.runtime_section)
"""
def __init__(
self,
skill_registry: SkillRegistry | None = None,
plugin_registry: PluginRegistry | None = None,
hook_runner: HookRunner | None = None,
sandbox_config: SandboxConfig | None = None,
guardrails: list[str] | None = None,
profile: PromptProfile | PromptProfileConfig | None = None,
workspace_root: str | None = None,
):
"""Initialise the prompt context builder.
Args:
skill_registry: Optional skill registry for building the
skills index section.
plugin_registry: Optional plugin registry (reserved for
future plugin-contributed prompt sections).
hook_runner: Optional hook runner used to invoke
``bootstrap_files`` hooks for project context injection.
sandbox_config: Optional default sandbox configuration
applied when no agent-specific override is provided.
guardrails: Optional default list of guardrail rule strings.
profile: Default prompt profile controlling section
verbosity. Defaults to :data:`PromptProfile.FULL`.
workspace_root: Explicit workspace root directory. When
``None``, the current working directory is used.
"""
self.skill_registry = skill_registry
self.plugin_registry = plugin_registry
self.hook_runner = hook_runner
self.sandbox_config = sandbox_config
self.guardrails = guardrails or []
self.default_profile_config = _resolve_profile_config(profile)
self.workspace_root = workspace_root
[docs] def build(
self,
agent_id: str | None = None,
tool_names: list[str] | None = None,
profile: PromptProfile | PromptProfileConfig | str | None = None,
) -> PromptContext:
"""Build all prompt context sections using default overrides.
Convenience wrapper around :meth:`build_with_overrides` that
forwards the most common parameters.
Args:
agent_id: Optional agent identifier for bootstrap hook
dispatch and per-agent customisation.
tool_names: Optional list of tool names available to the
agent in this run.
profile: Optional prompt profile override. When ``None``,
the builder's default profile is used.
Returns:
A :class:`PromptContext` with all applicable sections
populated.
"""
return self.build_with_overrides(agent_id=agent_id, tool_names=tool_names, profile=profile)
[docs] def build_with_overrides(
self,
agent_id: str | None = None,
tool_names: list[str] | None = None,
sandbox_config: SandboxConfig | None = None,
guardrails: list[str] | None = None,
enabled_skills: list[Skill] | None = None,
profile: PromptProfile | PromptProfileConfig | str | None = None,
) -> PromptContext:
"""Build all prompt context sections with agent-specific overrides.
This is the full-control entry point. Each parameter can override
the builder's defaults for a single invocation, enabling
per-agent customisation without mutating shared state.
Args:
agent_id: Optional agent identifier forwarded to bootstrap
hooks.
tool_names: Optional list of tool names available to the
agent.
sandbox_config: Optional per-agent sandbox configuration
that overrides the builder's default.
guardrails: Optional per-agent guardrail list that overrides
the builder's default.
enabled_skills: Optional list of resolved :class:`Skill`
objects whose instruction text should be injected.
profile: Optional prompt profile override. Accepts a
:class:`PromptProfile` enum, a string name, or a
:class:`PromptProfileConfig`. When ``None``, the
builder's default profile is used.
Returns:
A :class:`PromptContext` with each section populated (or
left empty) according to the resolved profile configuration.
"""
pcfg = _resolve_profile_config(profile) if profile is not None else self.default_profile_config
runtime_info = RuntimeInfo.capture(self.workspace_root)
ctx = PromptContext()
ctx.runtime_section = self._build_runtime(runtime_info) if pcfg.include_runtime_info else ""
ctx.workspace_section = self._build_workspace(runtime_info) if pcfg.include_workspace_info else ""
ctx.datetime_section = self._build_datetime(runtime_info) if pcfg.include_runtime_info else ""
ctx.reasoning_section = self._build_reasoning(pcfg) if pcfg.include_runtime_info else ""
ctx.sandbox_section = self._build_sandbox(sandbox_config=sandbox_config) if pcfg.include_sandbox_info else ""
ctx.skills_section = self._build_skills(pcfg) if pcfg.include_skills_index else ""
ctx.enabled_skills_section = (
self._build_enabled_skills(enabled_skills=enabled_skills, profile_config=pcfg)
if pcfg.include_enabled_skills
else ""
)
ctx.tools_section = self._build_tools(tool_names, pcfg) if pcfg.include_tools_list else ""
ctx.guardrails_section = self._build_guardrails(guardrails=guardrails) if pcfg.include_guardrails else ""
ctx.bootstrap_section = self._build_bootstrap(agent_id) if pcfg.include_bootstrap else ""
return ctx
[docs] def build_compact_prefix(
self,
agent_id: str | None = None,
tool_names: list[str] | None = None,
sandbox_config: SandboxConfig | None = None,
guardrails: list[str] | None = None,
enabled_skills: list[Skill] | None = None,
) -> str:
"""Build a system prompt prefix using the COMPACT profile.
The compact profile drops workspace/bootstrap sections and caps
skill instructions and tool lists, yielding a shorter prefix
suitable for sub-agent delegation.
Args:
agent_id: Optional agent identifier for hook dispatch.
tool_names: Optional list of available tool names.
sandbox_config: Optional sandbox configuration override.
guardrails: Optional guardrail list override.
enabled_skills: Optional list of enabled :class:`Skill`
objects.
Returns:
A compact system prompt prefix string.
"""
return self.assemble_system_prompt_prefix(
agent_id=agent_id,
tool_names=tool_names,
sandbox_config=sandbox_config,
guardrails=guardrails,
enabled_skills=enabled_skills,
profile=PromptProfile.COMPACT,
)
[docs] def build_minimal_prefix(
self,
agent_id: str | None = None,
tool_names: list[str] | None = None,
sandbox_config: SandboxConfig | None = None,
guardrails: list[str] | None = None,
enabled_skills: list[Skill] | None = None,
) -> str:
"""Build a system prompt prefix using the MINIMAL profile.
The minimal profile includes only sandbox info, guardrails, and
a short tool list (capped at 10 entries). All other sections are
omitted, producing the smallest useful prefix for internal
delegation.
Args:
agent_id: Optional agent identifier for hook dispatch.
tool_names: Optional list of available tool names.
sandbox_config: Optional sandbox configuration override.
guardrails: Optional guardrail list override.
enabled_skills: Optional list of enabled :class:`Skill`
objects.
Returns:
A minimal system prompt prefix string.
"""
return self.assemble_system_prompt_prefix(
agent_id=agent_id,
tool_names=tool_names,
sandbox_config=sandbox_config,
guardrails=guardrails,
enabled_skills=enabled_skills,
profile=PromptProfile.MINIMAL,
)
[docs] def build_none_prefix(self) -> str:
"""Build an OpenClaw-style identity-only system prompt prefix.
Returns only the bare identity line with no runtime sections,
useful when the caller supplies all context externally.
Returns:
A single-line identity string for the system prompt.
"""
return self.assemble_system_prompt_prefix(profile=PromptProfile.NONE)
def _build_runtime(self, info: RuntimeInfo) -> str:
"""Build the runtime environment section string.
Args:
info: Captured runtime information snapshot.
Returns:
A multi-line string describing the platform, Python version,
and Calute version.
"""
return (
f"[Runtime Context]\n"
f" Platform: {info.platform}\n"
f" Python: {info.python_version}\n"
f" Calute: v{info.calute_version}\n"
)
def _build_workspace(self, info: RuntimeInfo) -> str:
"""Build the workspace directory section string.
Args:
info: Captured runtime information snapshot.
Returns:
A string containing the working directory path and project
name.
"""
return f"[Workspace]\n Directory: {info.working_directory}\n Project: {info.workspace_name}\n"
def _build_datetime(self, info: RuntimeInfo) -> str:
"""Build the current date/time section string.
Args:
info: Captured runtime information snapshot.
Returns:
A string with the local timestamp and timezone name.
"""
return f"[Current Date & Time]\n Local time: {info.timestamp}\n Time zone: {info.timezone}\n"
def _build_reasoning(self, pcfg: PromptProfileConfig | None = None) -> str:
"""Build the reasoning/profile guidance section string.
Emits the active profile name and a general guidance reminder
that answers should be grounded in actual tool results.
Args:
pcfg: Optional profile config whose profile name is
displayed. Defaults to :data:`PromptProfile.FULL`.
Returns:
A multi-line reasoning guidance block string.
"""
profile_name = pcfg.profile.value if pcfg is not None else PromptProfile.FULL.value
return (
f"[Response Guidance]\n"
f" Profile: {profile_name}\n"
f" Guidance: answer from actual tool and workspace results; avoid speculative claims; keep internal reasoning private; and put the final answer in the normal assistant response content, not in a scratchpad or reasoning field.\n"
)
def _build_sandbox(self, sandbox_config: SandboxConfig | None = None) -> str:
"""Build the sandbox status section string.
Describes the sandbox mode and lists which tools are sandboxed
versus elevated. Returns an empty string when no sandbox
configuration is available.
Args:
sandbox_config: Optional override sandbox configuration. When
``None``, falls back to the builder's default
``sandbox_config``.
Returns:
A sandbox description block, or an empty string if sandbox
is not configured.
"""
config = sandbox_config or self.sandbox_config
if not config:
return ""
from ..security.sandbox import SandboxMode
if config.mode == SandboxMode.OFF:
return "[Sandbox] Mode: off (all execution on host)\n"
return (
f"[Sandbox]\n"
f" Mode: {config.mode.value}\n"
f" Sandboxed tools: {', '.join(sorted(config.sandboxed_tools)) or 'none'}\n"
f" Elevated tools: {', '.join(sorted(config.elevated_tools)) or 'none'}\n"
)
def _build_skills(self, pcfg: PromptProfileConfig | None = None) -> str:
"""Build the skills index section string from the skill registry.
Delegates to the skill registry's ``build_skills_index`` method
to produce a human-readable summary of all discovered skills.
Args:
pcfg: Optional profile config (reserved for future
profile-aware skill index formatting).
Returns:
A formatted skills index block, or an empty string when no
skill registry is configured or no skills are discovered.
"""
if not self.skill_registry:
return ""
index = self.skill_registry.build_skills_index()
return f"[Skills]\n{index}\n" if index else ""
def _build_enabled_skills(
self,
enabled_skills: list[Skill] | None = None,
profile_config: PromptProfileConfig | None = None,
) -> str:
"""Build the enabled-skill instruction sections.
Renders each enabled skill's prompt section and concatenates
them. When ``max_skill_instructions_length`` is set in the
profile config, individual skill sections are truncated to that
length.
Args:
enabled_skills: List of resolved :class:`Skill` objects to
render. Returns an empty string when ``None`` or empty.
profile_config: Optional profile config providing the
``max_skill_instructions_length`` truncation cap.
Returns:
A formatted block of enabled skill instructions, or an
empty string if no skills are provided.
"""
if not enabled_skills:
return ""
max_len = profile_config.max_skill_instructions_length if profile_config else None
sections: list[str] = []
for skill in enabled_skills:
section = skill.to_prompt_section()
if max_len is not None and len(section) > max_len:
section = section[:max_len] + "..."
sections.append(section)
rendered = "\n\n".join(sections)
return f"[Enabled Skill Instructions]\n{rendered}\n"
def _build_tools(
self,
tool_names: list[str] | None = None,
pcfg: PromptProfileConfig | None = None,
) -> str:
"""Build the available tools list section string.
When ``max_tools_listed`` is set in the profile config and the
tool list exceeds that cap, the output is truncated with a
summary count of remaining tools.
Args:
tool_names: List of tool name strings. Returns an empty
string when ``None`` or empty.
pcfg: Optional profile config providing the
``max_tools_listed`` truncation cap.
Returns:
A formatted available-tools block, or an empty string if no
tool names are provided.
"""
if not tool_names:
return ""
max_tools = pcfg.max_tools_listed if pcfg else None
if max_tools is not None and len(tool_names) > max_tools:
shown = tool_names[:max_tools]
remaining = len(tool_names) - max_tools
lines = [f" - {name}" for name in shown]
lines.append(f" ... and {remaining} more")
else:
lines = [f" - {name}" for name in tool_names]
return "[Available Tools]\n" + "\n".join(lines) + "\n"
def _build_guardrails(self, guardrails: list[str] | None = None) -> str:
"""Build the guardrails section string.
Args:
guardrails: Optional override guardrail list. When ``None``,
falls back to the builder's default ``guardrails``.
Returns:
A formatted guardrails block, or an empty string if no
guardrails are active.
"""
active_guardrails = self.guardrails if guardrails is None else guardrails
if not active_guardrails:
return ""
lines = ["[Guardrails]"]
for g in active_guardrails:
lines.append(f" - {g}")
return "\n".join(lines) + "\n"
def _build_bootstrap(self, agent_id: str | None = None) -> str:
"""Build the bootstrap/project context section by running hooks.
Invokes the ``bootstrap_files`` hook point and concatenates
all returned content. Hook results may be individual strings or
lists of strings.
Args:
agent_id: Optional agent identifier passed to the hook
runner for agent-specific bootstrap content.
Returns:
Concatenated bootstrap content, or an empty string if no
hook runner is configured or no results are returned.
"""
if not self.hook_runner or not self.hook_runner.has_hooks("bootstrap_files"):
return ""
results = self.hook_runner.run("bootstrap_files", agent_id=agent_id)
if not results:
return ""
sections = []
for content in results:
if isinstance(content, list):
sections.extend(content)
elif isinstance(content, str):
sections.append(content)
return "\n".join(sections) if sections else ""
[docs] def assemble_system_prompt_prefix(
self,
agent_id: str | None = None,
tool_names: list[str] | None = None,
sandbox_config: SandboxConfig | None = None,
guardrails: list[str] | None = None,
enabled_skills: list[Skill] | None = None,
profile: PromptProfile | PromptProfileConfig | str | None = None,
) -> str:
"""Build the full enriched prefix for a system prompt.
Assembles identity, tooling, safety, skills, workspace, sandbox,
runtime, execution policy, and output style blocks into a single
string that should be prepended to the agent's instructions in
the system message.
For the :data:`PromptProfile.NONE` profile, returns only a
single identity line with no runtime context sections.
Args:
agent_id: Optional agent identifier for hook dispatch and
per-agent customisation.
tool_names: Optional list of tool names available to the
agent.
sandbox_config: Optional sandbox configuration override.
guardrails: Optional guardrail list override.
enabled_skills: Optional list of enabled :class:`Skill`
objects.
profile: Optional prompt profile override. When ``None``,
the builder's default profile is used (which itself
defaults to :data:`PromptProfile.FULL`).
Returns:
The assembled system prompt prefix string with all
applicable blocks joined by double newlines.
"""
resolved_profile = _resolve_profile_config(profile) if profile is not None else self.default_profile_config
if resolved_profile.profile == PromptProfile.NONE:
return "You are Calute, a runtime-managed AI agent operating inside a controlled tool environment."
ctx = self.build_with_overrides(
agent_id=agent_id,
tool_names=tool_names,
sandbox_config=sandbox_config,
guardrails=guardrails,
enabled_skills=enabled_skills,
profile=resolved_profile,
)
parts = [
self._build_identity_block(resolved_profile),
self._build_tooling_block(ctx),
self._build_safety_block(ctx),
self._build_skill_block(ctx),
self._build_workspace_block(ctx),
self._build_sandbox_block(ctx),
self._build_runtime_block(ctx),
self._build_execution_policy_block(resolved_profile),
self._build_output_style_block(resolved_profile),
]
return "\n\n".join(part for part in parts if part)
def _build_identity_block(self, profile: PromptProfileConfig) -> str:
"""Build the identity block lines for the assembled system prompt.
For the FULL profile, describes the agent as a primary Calute
agent. For all other profiles, describes it as a delegated
sub-agent with narrower responsibilities.
Args:
profile: The resolved profile configuration determining
which identity variant to emit.
Returns:
A multi-line identity block string.
"""
if profile.profile == PromptProfile.FULL:
lines = [
"[Identity]",
"- You are Calute, a runtime-managed AI agent operating inside a controlled tool environment.",
"- Complete the user's task accurately, efficiently, and safely using the available tools, skills, and workspace context.",
"- Follow runtime policy, sandbox limits, and tool restrictions.",
]
else:
lines = [
"[Identity]",
"- You are Calute, a delegated sub-agent running inside a controlled runtime.",
"- Stay within the assigned subtask and return integration-friendly output.",
"- Follow runtime policy, sandbox limits, and tool restrictions.",
]
return "\n".join(lines)
def _build_tooling_block(self, ctx: PromptContext) -> str:
"""Build the tooling block including tool list and tool-use rules.
Combines the rendered tools section from *ctx* with a fixed set
of tool-use guidance rules.
Args:
ctx: The assembled prompt context containing the tools
section.
Returns:
A multi-line tooling block string.
"""
tools_section = ctx.tools_section.rstrip() if ctx.tools_section else "[Available Tools]\n - none"
tools_section_lower = tools_section.lower()
lines = [
"[Tooling]",
"Available tools in this run:",
tools_section,
"Tool rules:",
"- Use tools only when you need live external state, workspace contents, shell execution, or another real action you cannot complete from the current conversation alone.",
"- Do not use or simulate tools for greetings, simple arithmetic, direct explanations, summaries, or code-writing requests that can be answered directly.",
"- Do not repeat the same tool call with the same arguments if it did not make progress.",
"- If a tool result already answers the task, use it directly.",
"- For a simple tool-backed request, prefer one necessary tool call followed by the final answer instead of extended planning or multiple retries.",
"- If a tool fails, adjust strategy instead of blindly retrying.",
]
guidance_lines: list[str] = []
if "web.search_query" in tools_section_lower:
guidance_lines.append(
'- `web.search_query`: Use this when the user explicitly asks to search/look up/browse the web, or when the answer depends on live recent information, news, or source discovery. Pass a clean search phrase in `q`; prefer `search_type="news"` for latest/news queries and `"text"` for general research.'
)
guidance_lines.append(
"- Generic web-search follow-ups: If the user says something like `search the web`, `look it up`, or `find it` right after discussing a topic, infer the topic from the latest relevant user request instead of asking the same clarification again, then call `web.search_query` with that inferred query."
)
if "web.open" in tools_section_lower:
guidance_lines.append(
"- `web.open`: Use this after search when you need the contents of a specific result page, direct quotes, or details that are not in the search snippets. Do this before presenting a claim as confirmed, official, or verified."
)
if "web.find" in tools_section_lower:
guidance_lines.append(
"- `web.find`: Use this on an already opened page to jump to a specific term, section, or citation instead of guessing where the information is."
)
if "readfile" in tools_section_lower or "read_file" in tools_section_lower:
guidance_lines.append(
"- File-reading tools: Use them for project-specific facts, exact code behavior, config values, or anything the workspace can answer more reliably than memory."
)
if "listdir" in tools_section_lower or "list_dir" in tools_section_lower:
guidance_lines.append(
"- Directory-listing tools: Use them to discover repo structure or confirm what files exist before claiming paths from memory."
)
if (
"exec_command" in tools_section_lower
or "executeshell" in tools_section_lower
or "execute_shell" in tools_section_lower
):
guidance_lines.append(
"- Shell tools: Use them for real command execution, environment inspection, tests, and filesystem queries that require current machine state."
)
if guidance_lines:
lines.extend(["Tool selection guidance:", *guidance_lines])
lines.extend(
[
"Search grounding rules:",
"- If a web/search tool is available or has already been used in the conversation, do not claim that you cannot browse or access current information.",
"- Search snippets and result titles are leads, not verification. Phrase them as 'search results indicate' or 'the top result says' unless you opened a source and confirmed it.",
]
)
return "\n".join(lines)
def _build_safety_block(self, ctx: PromptContext) -> str:
"""Build the safety/guardrails block for the assembled system prompt.
Combines any active guardrails from *ctx* with fixed safety
rules that prevent sandbox bypass and fabricated outputs.
Args:
ctx: The assembled prompt context containing the guardrails
section.
Returns:
A multi-line safety block string.
"""
lines = ["[Safety]", "Safety guidance:"]
if ctx.guardrails_section:
lines.append(ctx.guardrails_section.rstrip())
else:
lines.append("- No additional runtime guardrails are configured for this run.")
lines.extend(
[
"Safety rules:",
"- Do not try to bypass oversight, sandboxing, or tool restrictions.",
"- Do not invent tool results, file contents, or execution outcomes.",
"- If blocked by runtime policy or sandbox limits, say so plainly.",
]
)
return "\n".join(lines)
def _build_skill_block(self, ctx: PromptContext) -> str:
"""Build the skills and enabled-skill instructions block.
Combines the skills index and enabled-skill instructions from
*ctx* with rules governing skill usage.
Args:
ctx: The assembled prompt context containing skills and
enabled-skills sections.
Returns:
A multi-line skills block string, or an empty string if
neither the skills index nor enabled-skill instructions
are present.
"""
if not ctx.skills_section and not ctx.enabled_skills_section:
return ""
lines = ["[Skills & Instructions]"]
if ctx.skills_section:
lines.extend(["Available skills:", ctx.skills_section.rstrip()])
if ctx.enabled_skills_section:
lines.extend(["Enabled skill instructions:", ctx.enabled_skills_section.rstrip()])
lines.extend(
[
"Skill rules:",
"- Use enabled skills as task-specific operating instructions.",
"- If a skill is listed but not fully injected, load or apply it only when relevant.",
"- Do not assume a skill exists unless it is present in runtime context.",
]
)
return "\n".join(lines)
def _build_workspace_block(self, ctx: PromptContext) -> str:
"""Build the workspace context block including bootstrap content.
Combines the workspace directory section and bootstrap-injected
project context with rules for treating workspace files as the
source of truth.
Args:
ctx: The assembled prompt context containing workspace and
bootstrap sections.
Returns:
A multi-line workspace block string, or an empty string if
neither section is populated.
"""
if not ctx.workspace_section and not ctx.bootstrap_section:
return ""
lines = ["[Workspace Context]"]
if ctx.workspace_section:
lines.append(ctx.workspace_section.rstrip())
if ctx.bootstrap_section:
lines.extend(["Project/bootstrap context:", ctx.bootstrap_section.rstrip()])
lines.extend(
[
"Workspace rules:",
"- Treat workspace files as the source of truth for project-specific behavior.",
"- Prefer minimal, targeted changes over broad rewrites.",
]
)
return "\n".join(lines)
def _build_sandbox_block(self, ctx: PromptContext) -> str:
"""Build the sandbox runtime block if sandbox info is present.
Renders the sandbox mode details with rules enforcing correct
sandboxed vs. elevated tool handling.
Args:
ctx: The assembled prompt context containing the sandbox
section.
Returns:
A multi-line sandbox block string, or an empty string if
no sandbox information is available.
"""
if not ctx.sandbox_section:
return ""
lines = [
"[Sandbox Runtime]",
ctx.sandbox_section.rstrip(),
"Sandbox rules:",
"- Treat sandboxed tools as sandboxed.",
"- Elevated execution is exceptional and must be explicit.",
"- Never describe host execution as sandboxed if it was not.",
]
return "\n".join(lines)
def _build_runtime_block(self, ctx: PromptContext) -> str:
"""Build the runtime info block combining runtime, datetime, and reasoning sections.
Concatenates the runtime environment, date/time, and reasoning
guidance sections under a single ``[Runtime]`` header.
Args:
ctx: The assembled prompt context containing runtime,
datetime, and reasoning sections.
Returns:
A multi-line runtime block string, or an empty string if
all three sub-sections are empty.
"""
if not ctx.runtime_section and not ctx.datetime_section and not ctx.reasoning_section:
return ""
lines = ["[Runtime]"]
if ctx.runtime_section:
lines.append(ctx.runtime_section.rstrip())
if ctx.datetime_section:
lines.append(ctx.datetime_section.rstrip())
if ctx.reasoning_section:
lines.append(ctx.reasoning_section.rstrip())
return "\n".join(lines)
def _build_execution_policy_block(self, profile: PromptProfileConfig) -> str:
"""Build the execution policy block appropriate for the given profile.
The FULL profile emits a detailed numbered policy, while all
other profiles emit a condensed bullet-list variant.
Args:
profile: The resolved profile configuration controlling
which policy variant to emit.
Returns:
A multi-line execution policy block string.
"""
if profile.profile == PromptProfile.FULL:
lines = [
"[Execution Policy]",
"1. Understand the request and use the workspace context first.",
"2. Choose the smallest correct action that moves the task forward.",
"3. Use tools only when you need missing live information, file contents, execution, or verification that cannot be done from the conversation alone.",
"4. Do not simulate tool calls or wrap normal answers in tool/XML markup.",
"5. If one successful tool result is enough, stop and give the final answer immediately.",
"6. After tool use, answer from the actual result.",
"7. Surface blockers, assumptions, and risks clearly.",
"8. Do not loop.",
]
else:
lines = [
"[Execution Policy]",
"- Stay within the assigned subtask.",
"- Use tools only when needed for missing live information or real actions.",
"- Do not simulate tool calls or emit tool/XML wrappers in normal answers.",
"- If one tool result is enough, answer immediately instead of continuing to plan.",
"- Answer from actual tool and workspace results.",
"- Keep output compact and integration-friendly.",
]
return "\n".join(lines)
def _build_output_style_block(self, profile: PromptProfileConfig) -> str:
"""Build the output style block; only emitted for the FULL profile.
Provides stylistic guidance encouraging precise, technical, and
results-oriented output. Omitted for non-FULL profiles to save
tokens.
Args:
profile: The resolved profile configuration. Only the FULL
profile triggers output.
Returns:
A multi-line output style block string for the FULL profile,
or an empty string for all other profiles.
"""
if profile.profile != PromptProfile.FULL:
return ""
return "\n".join(
[
"[Output Style]",
"- Be precise, technical, and pragmatic.",
"- Prefer concrete outcomes over general advice.",
"- Keep internal reasoning out of the visible answer unless the user explicitly asks for it.",
"- Put the user-facing answer in the normal assistant response content, not in a scratchpad or reasoning field.",
"- If code or files were changed, mention the real result.",
"- If tests were run, report the actual scope and outcome.",
]
)