Skip to content
Merged
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
1 change: 1 addition & 0 deletions src/rlm/acp.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ class _LimitsSnapshot(_ContractModel):
max_compactions: int | None = Field(default=None, gt=0)
max_compaction_attempts: int = Field(gt=0)
allow_git: bool
network_guidance: bool


class _SemanticEdge(_ContractModel):
Expand Down
4 changes: 4 additions & 0 deletions src/rlm/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@ class ExecutionPolicy(_ConfigModel):
"""Tree-total cap on sub-agent spawns. None (default) = uncapped; ``max_total_tokens``
still bounds the tree."""
allow_git: bool = False
network_guidance: bool = True
"""Include prompt guidance that assumes outbound network access (installing extra
kernel packages, the online-solutions clause of the Git guard). Disable it for
boxes with no network."""


class TcpExecutionConfig(_ConfigModel):
Expand Down
3 changes: 3 additions & 0 deletions src/rlm/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ def __init__(
self.max_depth = config.policy.max_depth
self.depth = config.invocation.depth
self.allow_git = config.policy.allow_git
self.network_guidance = config.policy.network_guidance

# Task MCP tool servers to expose as IPython skills.
self.mcp_servers = validate_mcp_servers(mcp_servers or {})
Expand Down Expand Up @@ -1500,6 +1501,7 @@ def execution_snapshot(self) -> dict:
"max_compactions": self.runtime_config.policy.max_compactions,
"max_compaction_attempts": self.max_compaction_attempts,
"allow_git": self.runtime_config.policy.allow_git,
"network_guidance": self.runtime_config.policy.network_guidance,
},
"semantic_edges": self._semantic_edges.snapshot(),
}
Expand All @@ -1519,6 +1521,7 @@ def _load_system_prompt(self, active_tools: list[BuiltinTool]) -> str:
allow_recursion=self.depth < self.max_depth,
delegation_prompt=self.runtime_config.policy.delegation_prompt,
allow_git=self.allow_git,
network_guidance=self.network_guidance,
active_tools=active_tools,
shell_skills=get_installed_skills(),
skill_docs=discover_skill_docs(SKILLS_DIR) if SKILLS_DIR else [],
Expand Down
28 changes: 22 additions & 6 deletions src/rlm/prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,21 @@
)

SHELL_TOOL_NAMES = frozenset({"ipython", "bash"})
GIT_HISTORY_GUARD_PROMPT = (
"Do not cheat by using online solutions or hints specific to this task, or "
"by copying or inferring solutions from other branches, tags, remotes, "
_GIT_HISTORY_GUARD_ONLINE = (
"using online solutions or hints specific to this task, or by "
)
_GIT_HISTORY_GUARD_LOCAL = (
"copying or inferring solutions from other branches, tags, remotes, "
"reflogs, or broad git history in the project, or by cloning other "
"repositories to read their history. Git commands that reach beyond the "
"current branch are refused: `git log --all` and similar history options, "
"history subcommands given another ref (`git show origin/main`), listing "
"branches, tags, remotes or the reflog, and `clone`, `fetch` or `pull`."
)
GIT_HISTORY_GUARD_PROMPT = (
"Do not cheat by " + _GIT_HISTORY_GUARD_ONLINE + _GIT_HISTORY_GUARD_LOCAL
)
OFFLINE_GIT_HISTORY_GUARD_PROMPT = "Do not cheat by " + _GIT_HISTORY_GUARD_LOCAL
PROJECT_ENV_PROMPT = (
"The ipython kernel is an isolated venv without the project's packages — "
"never import project modules there. Everything that executes project code "
Expand All @@ -47,8 +53,11 @@
"`await job.result()` collects any job. Use `rlm.services.start` for a server or daemon that must keep "
"running after you finish. " + PROJECT_ENV_PROMPT
)
KERNEL_PREINSTALLED_PROMPT = (
"Pre-installed in the kernel venv: " + ", ".join(BASE_TOOLKIT) + "."
)
KERNEL_PACKAGES_PROMPT = (
"Pre-installed in the kernel venv: " + ", ".join(BASE_TOOLKIT) + ". "
KERNEL_PREINSTALLED_PROMPT + " "
"Install extra packages with `!uv pip install <pkg>` in a code cell — that "
"targets the kernel venv (a uv-managed venv with no pip module)."
)
Expand Down Expand Up @@ -378,6 +387,7 @@ def build_system_prompt(
allow_recursion: bool,
delegation_prompt: bool = False,
allow_git: bool,
network_guidance: bool = True,
active_tools: list[BuiltinTool],
shell_skills: list[str] | None = None,
skill_docs: list["SkillDoc"] | None = None,
Expand Down Expand Up @@ -447,7 +457,9 @@ def build_system_prompt(
RUNTIME_PROMPT,
HISTORY_PROMPT,
IPYTHON_CONTROL_PROMPT,
KERNEL_PACKAGES_PROMPT,
KERNEL_PACKAGES_PROMPT
if network_guidance
else KERNEL_PREINSTALLED_PROMPT,
]
)
if can_delegate:
Expand Down Expand Up @@ -491,7 +503,11 @@ def build_system_prompt(
+ ". Discover CLI usage with `<tool> --help`. Other listed Python tools are IPython-only."
)
if _should_include_git_history_guard(active_tools, allow_git):
parts.append(GIT_HISTORY_GUARD_PROMPT)
parts.append(
GIT_HISTORY_GUARD_PROMPT
if network_guidance
else OFFLINE_GIT_HISTORY_GUARD_PROMPT
)
return "\n\n".join(parts)


Expand Down
2 changes: 2 additions & 0 deletions tests/test_acp.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def _runtime_metadata(**overrides: Any) -> dict[str, Any]:
"max_concurrent_subagents": 4,
"max_subagent_calls": None,
"allow_git": False,
"network_guidance": False,
},
"system_prompt_path": None,
"append_to_system_prompt": None,
Expand Down Expand Up @@ -154,6 +155,7 @@ def execution_snapshot(self) -> dict[str, Any]:
"max_compactions": None,
"max_compaction_attempts": 5,
"allow_git": False,
"network_guidance": False,
},
"semantic_edges": {"edges": []},
}
Expand Down
2 changes: 2 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def test_runtime_config_redacts_secrets():
max_compactions=3,
max_compaction_attempts=7,
allow_git=True,
network_guidance=False,
),
skills=("search", "edit"),
kernel_env=(("TASK_TOKEN", "task-secret"),),
Expand All @@ -44,6 +45,7 @@ def test_runtime_config_redacts_secrets():
max_concurrent_subagents=4,
max_subagent_calls=None,
allow_git=True,
network_guidance=False,
)
assert config.skills == ("search", "edit")
assert config.kernel_env == (("TASK_TOKEN", "task-secret"),)
Expand Down
18 changes: 18 additions & 0 deletions tests/test_prompt.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@
EDIT_SKILL_PROMPT,
GIT_HISTORY_GUARD_PROMPT,
IPYTHON_CONTROL_PROMPT,
KERNEL_PACKAGES_PROMPT,
KERNEL_PREINSTALLED_PROMPT,
MCP_DISCOVERY_PROMPT,
OFFLINE_GIT_HISTORY_GUARD_PROMPT,
SEARCH_SKILL_PROMPT,
build_system_prompt,
)
Expand All @@ -28,13 +31,15 @@ def _prompt(
*,
installed_skills: list[str] | None = None,
allow_git: bool = False,
network_guidance: bool = True,
mcp_servers: list[dict] | None = None,
) -> str:
return build_system_prompt(
"/repo",
installed_skills or [],
allow_recursion=False,
allow_git=allow_git,
network_guidance=network_guidance,
active_tools=active_tools,
mcp_servers=mcp_servers,
)
Expand All @@ -50,6 +55,19 @@ def test_git_history_guard_prompt_included_for_shell_tools():
assert "`git log --all`" in prompt and "`clone`" in prompt


def test_network_guidance_off_drops_only_network_dependent_sentences():
online = _prompt([_Tool("ipython")])
offline = _prompt([_Tool("ipython")], network_guidance=False)

assert KERNEL_PACKAGES_PROMPT in online and GIT_HISTORY_GUARD_PROMPT in online
assert KERNEL_PREINSTALLED_PROMPT in offline
assert OFFLINE_GIT_HISTORY_GUARD_PROMPT in offline
assert "uv pip install" not in offline
assert "online solutions" not in offline
assert "Do not cheat by copying or inferring solutions" in offline
assert "`git log --all`" in offline and "`clone`" in offline


def test_git_history_guard_prompt_omitted_when_unrestricted():
assert GIT_HISTORY_GUARD_PROMPT not in _prompt([_Tool("ipython")], allow_git=True)

Expand Down
Loading