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
13 changes: 9 additions & 4 deletions src/rlm/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,13 @@ def _positive_int(value: str | int, name: str) -> int:


def _summarize_at_tokens(value: str | int | None) -> int | None:
"""Unset -> the 256k default; "" or "0" -> disabled; else a positive threshold."""
if value is None:
return 256_000
if value in ("", "0", 0):
return None
parsed = _optional_positive_int(value, "summarize_at_tokens")
if value not in (None, "") and parsed is None:
if parsed is None:
raise ValueError(f"summarize_at_tokens must be positive (got {value})")
return parsed

Expand Down Expand Up @@ -129,10 +134,10 @@ def child(self) -> InvocationContext:
class ExecutionPolicy(_ConfigModel):
"""Resource and context-management policy for one RLM engine."""

max_depth: int = Field(default=0, ge=0)
max_depth: int = Field(default=1, ge=0)
exec_timeout: int = Field(default=300, gt=0)
max_tokens: int | None = Field(default=None, gt=0)
summarize_at_tokens: int | None = Field(default=None, gt=0)
summarize_at_tokens: int | None = Field(default=256_000, gt=0)
max_compactions: int | None = Field(default=None, gt=0)
max_concurrent_subagents: int = Field(default=4, gt=0)
max_subagent_calls: int = Field(default=64, gt=0)
Expand Down Expand Up @@ -166,7 +171,7 @@ def from_env(
) -> RuntimeConfig:
env = os.environ if environ is None else environ
raw_skills = env.get("RLM_SKILLS")
max_depth = int(env.get("RLM_MAX_DEPTH", "0"))
max_depth = int(env.get("RLM_MAX_DEPTH", "1"))
default_concurrency = max(4, max_depth)
max_concurrent_subagents = _positive_int(
env.get("RLM_MAX_CONCURRENT_SUBAGENTS", str(default_concurrency)),
Expand Down
22 changes: 11 additions & 11 deletions tests/test_acp.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ async def test_engine_prompt_preserves_conversation(session):
first = await engine.prompt("one")
second = await engine.prompt("two")
finally:
engine.close()
await engine.aclose()

assert first.answer == "first"
assert second.answer == "second"
Expand Down Expand Up @@ -262,7 +262,7 @@ async def test_engine_prompt_preserves_ipython_kernel(session):
await engine.prompt("remember a value")
result = await engine.prompt("use that value")
finally:
engine.close()
await engine.aclose()

tool_messages = [
message for message in client.calls[-1]["messages"] if message["role"] == "tool"
Expand Down Expand Up @@ -295,7 +295,7 @@ async def block_first_prompt(**kwargs):
try:
result = await engine.prompt("continue")
finally:
engine.close()
await engine.aclose()

assert result.answer == "continued"
assert result.turns == 1
Expand Down Expand Up @@ -330,7 +330,7 @@ async def flaky_first_call(**kwargs):
model="test-model",
provider=ProviderConfig(base_url=None, api_key="test-key"),
invocation=InvocationContext(),
policy=ExecutionPolicy(summarize_at_tokens=1),
policy=ExecutionPolicy(summarize_at_tokens=1, max_depth=0),
Comment thread
cursor[bot] marked this conversation as resolved.
)
engine = RLMEngine(
client=client, # type: ignore[arg-type]
Expand All @@ -341,7 +341,7 @@ async def flaky_first_call(**kwargs):
try:
result = await engine.prompt("compact")
finally:
engine.close()
await engine.aclose()

assert result.answer == "done"
assert (
Expand Down Expand Up @@ -391,7 +391,7 @@ async def block_prompt(**kwargs):
pending.cancel()
with pytest.raises(asyncio.CancelledError):
await pending
engine.close()
await engine.aclose()

meta = json.loads((Path(session.dir) / "meta.json").read_text())
assert meta["status"] == "running"
Expand Down Expand Up @@ -424,7 +424,7 @@ async def test_compaction_counts_seed_prompt(session):
try:
await engine._compact_branch(messages, turn=0, active_tools=[])
finally:
engine.close()
await engine.aclose()

assert engine._metrics.num_compactions == 1
assert engine._metrics.compaction_chars_dropped_mean == len("original promptwork")
Expand All @@ -445,7 +445,7 @@ async def test_engine_failed_prompt_can_be_retried(session):
try:
result = await engine.prompt("continue")
finally:
engine.close()
await engine.aclose()

assert result.answer == "continued"
assert result.turns == 1
Expand Down Expand Up @@ -496,7 +496,7 @@ async def test_failed_prompt_restores_pre_compaction_context(session):
try:
result = await engine.prompt("continue")
finally:
engine.close()
await engine.aclose()

request_ids = [
call["extra_headers"]["X-ACP-Lineage-Request-ID"] for call in client.calls
Expand Down Expand Up @@ -563,7 +563,7 @@ def shutdown(self):
try:
result = await engine.prompt("continue")
finally:
engine.close()
await engine.aclose()

assert result.answer == "continued"
assert repl.finished is True
Expand Down Expand Up @@ -614,7 +614,7 @@ async def test_engine_cancelled_tool_recovers_kernel(session, tmp_path):
try:
result = await engine.prompt("continue")
finally:
engine.close()
await engine.aclose()

tool_messages = [
message for message in client.calls[-1]["messages"] if message["role"] == "tool"
Expand Down
12 changes: 12 additions & 0 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,15 @@ def test_runtime_config_rejects_unsafe_recursive_and_environment_values():
provider.headers["Idempotency-Key"] = "forged-after-construction"
with pytest.raises(ValueError, match="reserved names"):
make_client(provider)


def test_default_policy_enables_compaction_and_recursion():
config = RuntimeConfig.from_env(environ={})
assert config.policy.summarize_at_tokens == 256_000
assert config.policy.max_depth == 1


def test_summarize_at_tokens_disabled_by_zero_or_empty():
for raw in ("", "0"):
config = RuntimeConfig.from_env(environ={"RLM_SUMMARIZE_AT_TOKENS": raw})
assert config.policy.summarize_at_tokens is None
Loading