Skip to content

feat: terse persona prompt, bash+ipython default tools, actionable compaction handoff - #142

Merged
snimu merged 12 commits into
mainfrom
feat/terse-persona-prompt
Aug 26, 2026
Merged

feat: terse persona prompt, bash+ipython default tools, actionable compaction handoff#142
snimu merged 12 commits into
mainfrom
feat/terse-persona-prompt

Conversation

@samsja

@samsja samsja commented Aug 21, 2026

Copy link
Copy Markdown
Member

Summary

The rlm harness, rebuilt on evidence from a k3-focused ablation campaign (9 models, up to 128 tasks/arm, SWE-rebench-V2):

  1. Terse coding-agent persona replaces the verbose default system prompt (~3.2kB -> ~1.4kB)
  2. RLM_TOOLING preset — dual (default) | tools | skills — one knob for how shell/edit are exposed
  3. Kernel-isolation prompt line (stops project imports into the kernel venv)
  4. Actionable compaction handoff (fenced command blocks + numbered next steps)
  5. bash tool/skill share one guarded runner (bash -c, git-history guard, harness timeout); output-clipping removed

The starting point: something was wrong

kimi-k3 on stock rlm vs a plain bash+edit agent, same tasks:

setup solve (easy 32) reasoning tok/turn solve (SWE-bench Pro 32)
stock rlm (ipython-only, stock prompt) 0.53 327 0.75
bash+edit agent (verifiers harness) 0.69 268 0.94

Ablations isolated the cause: the verbose prompt (not the ipython interface, not %%bash syntax). Same tools, persona prompt: 209-232 tok/turn, solve 0.62-0.75. Frontier models are insensitive either way; kimi-k3 and opus-5 are prompt-sensitive (~2x reasoning inflation under the stock prompt).

Final ablation: the three RLM_TOOLING presets (128 tasks, kimi-k3, same seeded set)

preset n solve turns reason/turn error turns
tools 128 0.711 36.1 216 0.8%
skills (with triple-quote guidance) 125 0.704 33.5 256 2.5%
dual 128 0.695 35.4 239 2.1%

Converged on solve and total reasoning (~8k tok). tools is cleanest, skills fewest turns (REPL cells batch multiple actions), dual within noise of both.

The skills preset needed two fixes, each isolated on the same 32 tasks:

skills variant turns error turns
buggy skill (ran /bin/sh) + no guidance 53.5 4.2%
+ shell-parity fix (bash -c) 35.9 5.0%
+ triple-quote prompt line 33.5 2.5%

The bugfix bought the turns; the prompt line bought the errors. The skills preset auto-renders the validated line: "Run shell with out = await bash(\'\'\'command here\'\'\') — always triple-quote the command so shell quotes and multi-line scripts never need escaping..."

Cross-model check (GLM-5.3 @32): tools 0.61/1.3% err, skills 0.63 but 2.4x the reasoning tokens and 3.7% err, dual 0.59/0.6% err — skills-only is kimi-validated, not universal; hence dual as default (never the outlier on any model; both alternatives one env var away).

Kernel-isolation line

kimi imported project modules into the rlm kernel (~25 attempts/32 rollouts, 1 in 5 hitting ModuleNotFoundError). The line "The ipython kernel is an isolated venv without the project's packages... everything that executes project code goes through bash" drops kernel project-imports 5 -> 0, ImportErrors -> 0, at lower reasoning (161 vs 182 tok/turn on the probe set) — informational phrasing, no prohibition cost.

Compaction

CHECKPOINT_COMPACTION_PROMPT now demands runnable handoffs (exact repro/test commands + pending edits as fenced code blocks, numbered next steps): validated 0 -> ~4 command blocks per handoff, compacted-rollout solve 0.75-0.88 vs 0.53 under the stock prompt. Also checked: advance compaction notice in the system prompt is unnecessary (models stash state naturally; the at-compaction REPL note suffices; an explicit stash line doubles state-keeping but buys no solve).

Notes


Note

Medium Risk
Changes default tooling, prompts, and compaction handoffs that directly steer agent behavior; bash/edit run on the host cwd with file writes, though git guards and timeouts are centralized.

Overview
Default agent surface moves from IPython-only to native bash, edit, and ipython tools, with RLM_TOOLING (dual | tools | skills) and RLM_BUILTIN_TOOLS to override. When RLM_SKILLS is unset, skills now follow the preset via preset_skills() instead of an empty list.

rlm.tools.bash adds shared run_bash (guarded bash -c, cwd, allow_git) used by BashTool, EditTool, and the bash skill; the IPython kernel gets RLM_EXEC_TIMEOUT / RLM_ALLOW_GIT at startup from engine policy.

System prompt is shorter and tool-aware: coding-agent role lines per active tool, kernel-isolation guidance (PROJECT_ENV_PROMPT), conditional bash-skill triple-quote hints, and trimmed IPython/edit copy. Compaction CHECKPOINT_COMPACTION_PROMPT now requires fenced repro commands, pending edit calls, and numbered next steps.

Reviewed by Cursor Bugbot for commit 41e2698. Bugbot is set up for automated code reviews on this repo. Configure here.

samsja added 2 commits August 21, 2026 05:34
Replace the "general purpose agent / break down into sub-tasks" role framing
and the long IPYTHON_CONTROL_PROMPT ("long-lived notebook ... assign results
to named variables") with a terse coding-agent persona plus one factual
%%bash/project-env line.

Motivation: a 9-model x 8-arm interface/prompt ablation on SWE-rebench (32
tasks/arm, kimi-k3) found the verbose notebook framing - not the ipython
interface, not %%bash syntax - triggers heavy per-turn deliberation in some
models (kimi-k3: 327 reasoning tok/turn and 0.53 solve under the stock prompt
vs 209 tok/turn and 0.73 solve with this persona style, same tools, same
tasks; -20% output tokens). Frontier models were insensitive to the change.
…tion handoff

- New native `bash` builtin tool (one command per call, fresh subshell, git guard,
  output clipping). Default tool set is now bash + ipython (RLM_BUILTIN_TOOLS
  overrides, e.g. "ipython" for a REPL-only agent).
- `edit` builtin skill enabled by default (RLM_SKILLS="" disables).
- System prompt names each capability in one line (persona style); the %%bash
  control line only renders for REPL-only configs, replaced by the project-env
  rule otherwise.
- CHECKPOINT_COMPACTION_PROMPT now demands an actionable handoff: exact repro/test
  commands and pending edits as fenced code blocks + numbered next steps
  (validated to make post-compaction resumption concrete: 0 -> ~4 command blocks
  per handoff, solve-rate neutral-to-positive).

Motivation: 32-task interface ablations (kimi-k3) show the bash+ipython+edit-skill
config is the best balanced setup (reasoning cost 2nd-lowest of 9 arms tested,
full REPL powers retained); tool-availability is otherwise solve-neutral across
9 models. Tests: 112 passed.
@samsja samsja changed the title feat: terse coding-agent persona for the default system prompt feat: terse persona prompt, bash+ipython default tools, actionable compaction handoff Aug 21, 2026
samsja added 4 commits August 21, 2026 23:39
- EditTool joins the native tool set; default RLM_BUILTIN_TOOLS = bash, edit, ipython
- bash skill added; default RLM_SKILLS = bash, edit — both actions callable from
  the REPL too (`await bash(...)`, `await edit(...)`) for mixing with Python in one
  cell or avoiding shell quoting
- prompt names each route in one line; edit-skill line reframed as the in-REPL
  alternative (multiline/quote-heavy payloads) rather than the preferred path

Dual-route ablation (32 tasks, kimi-k3): native tools win revealed preference 99:1,
solve 0.74 / 199 reasoning tok/turn (best tier); the skill calls that do occur are
rational (quoting escape, output separation, py-text-to-shell). Keeping skills
registered costs nothing and covers that tail.
"The ipython kernel is an isolated venv without the project's packages — never
import project modules there. Everything that executes project code (tests,
repros, imports) goes through bash with the project's interpreter."

kimi-k3 was importing project modules into the rlm kernel (~25 attempts / 32
rollouts, ~1 in 5 hitting ModuleNotFoundError) despite the old softer wording.
Probes (8 tasks x 2 per arm): mechanism-explaining variants drop kernel
project-imports 5 -> 0 with zero ImportErrors and no reasoning inflation
(157-186 tok/turn vs 182 baseline). This wording is the config-agnostic
variant (no skill names or activity examples).
create_subprocess_shell runs through /bin/sh (dash on debian images), so
bashisms — process substitution <(...), [[ ]], etc. — failed in the skill
while working in the bash tool (observed: `sh: Syntax error: "(" unexpected`).
Exec ["bash", "-c", command] explicitly, exactly like tools/bash.py.
samsja added 3 commits August 25, 2026 20:25
Extract run_bash() in tools/bash.py (empty-command check, git-history guard,
bash -c, timeout, exit-code + clip) and call it from both BashTool.execute and
the bash skill — one execution path, byte-identical semantics. The skill also
adopts the harness exec timeout (RLM_EXEC_TIMEOUT, fallback 300) as its default
and now enforces the git guard it previously bypassed. Tests for both.
One knob selecting how shell/edit are exposed:
- dual   (default): native bash+edit tools AND bash+edit REPL skills
- tools:  native tools only (REPL stays, no bash/edit skills)
- skills: REPL-only; bash+edit as skills, with the validated triple-quote
  shell guidance rendered automatically in the prompt

RLM_BUILTIN_TOOLS / RLM_SKILLS remain as expert overrides on top.

128-task ablation (kimi-k3): the three modes converge on solve (0.69-0.72)
and total reasoning (~7k); tools is cleanest (0.9% error turns), skills is
fewest turns (29.8), dual within noise of both — hence dual as default.
GLM-5.2/5.3 partials favor dual on robustness.
snimu pushed a commit that referenced this pull request Aug 26, 2026
…145)

## Summary

Adds a built-in `bash` skill (enabled via `RLM_SKILLS`): `await
bash(command=...)` runs a shell command from the REPL and returns
combined stdout/stderr as a string (exit code appended when nonzero,
output clipped at 30k chars).

Why: lets the agent mix shell with Python in one cell — capture output
as a value, parse it programmatically, or sidestep shell-quoting by
building commands as triple-quoted Python strings.

Implementation note: executes via `["bash", "-c", command]` explicitly
rather than `create_subprocess_shell`, which resolves to `/bin/sh` (dash
on debian images) and rejects bashisms — we hit `sh: Syntax error: "("
unexpected` on process substitution in ablation runs before pinning this
down. A parity test (`cat <(echo ...)`) guards it.

Tests: 4 new (output capture, bashism parity, exit-code reporting,
stderr merging); suite green except 6 pre-existing `test_acp.py`
failures that also fail on clean main in this environment.

Extracted from #142 so it can ship independently.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Introduces arbitrary shell execution from the REPL; mitigated by
shared git-history blocking and timeouts, but still expands the agent’s
command surface.
> 
> **Overview**
> Adds a **`bash` built-in skill** (enable with `RLM_SKILLS`) so agents
can **`await bash(...)`** from IPython and get shell output as a string
instead of only using separate shell tools.
> 
> The new `rlm.skills.bash` module runs **one command per call** via
**`bash -c`** (not `/bin/sh`), merges stdout and stderr, appends
**`[exit code N]`** on failure, honors **`RLM_EXEC_TIMEOUT`** (default
300s), and applies the same **`find_blocked_command` / git-history
guard** as other shell paths. Registration in `_BUILTIN_SKILLS` wires it
into the existing session stub / pre-import flow like `edit`.
> 
> Tests cover echo output, bash-specific syntax (process substitution),
nonzero exits, stderr, git guard behavior, timeouts, and that
**`enable_builtin_skills(["bash"], ...)`** writes the expected re-export
stub.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
bab8cf6. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
@samsja
samsja force-pushed the feat/terse-persona-prompt branch from 234948b to 9b607b0 Compare August 26, 2026 19:13
@snimu
snimu marked this pull request as ready for review August 26, 2026 20:10
snimu
snimu previously approved these changes Aug 26, 2026

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 9b607b0. Configure here.

Comment thread src/rlm/tools/bash.py
Comment thread src/rlm/tools/bash.py
@snimu
snimu self-requested a review August 26, 2026 20:16
…paths to cwd

run_bash now threads allow_git through to find_blocked_command (previously the
guard followed only RLM_ALLOW_GIT, diverging from the ipython tool which honors
the execution policy); EditTool resolves relative paths against context.cwd
like BashTool does, instead of the process working directory.

Reported by cursor bugbot on #142.
@samsja

samsja commented Aug 26, 2026

Copy link
Copy Markdown
Member Author

Both fixed in the latest commit — run_bash threads allow_git from ToolContext into find_blocked_command (matching the ipython tool's policy-over-env behavior), and EditTool now resolves relative paths against context.cwd like BashTool. Tests added for both.

The kernel environment is sanitized (#134), so RLM_EXEC_TIMEOUT and
RLM_ALLOW_GIT never reached the kernel — the bash skill silently fell back to
its 300s default timeout and always-blocked git policy, diverging from the
harness execution policy that the bash/ipython tools honor. IPythonREPL now
takes exec_timeout/allow_git from the engine and exports them in kernel
startup, so skills see the same policy as tools. Kernel-policy test added.

Same drift family as the earlier bugbot findings (parallel paths, one policy).
@snimu
snimu merged commit 2838f05 into main Aug 26, 2026
9 checks passed
@snimu
snimu deleted the feat/terse-persona-prompt branch August 26, 2026 20:44
samsja added a commit that referenced this pull request Aug 28, 2026
## Summary

Two default changes:

| setting | before | after | disable/override |
|---|---|---|---|
| `summarize_at_tokens` | `None` (compaction off) | **256,000** |
`RLM_SUMMARIZE_AT_TOKENS=""` or `"0"` |
| `max_depth` | 0 (no sub-agents) | **1** | `RLM_MAX_DEPTH=0` |

## Why

- **Compaction on by default**: without it, long trajectories die at the
model context limit. 256k is a safety-net threshold — typical rollouts
never touch it (SWE medians are ~10-30k), but the rare monster
trajectory compacts and survives instead of failing. Compaction behavior
(handoff quality, post-compaction recovery via the surviving kernel +
conversation log) has been validated in the ablations backing #142;
at-threshold rollouts solved at 0.75-0.88 with the improved handoff
prompt.
- **`max_depth=1` by default**: one level of `await rlm('sub-task')` is
available out of the box, matching how the harness is deployed in
practice.

## Notes

- Empty/`0` env values for `RLM_SUMMARIZE_AT_TOKENS` now mean "disabled"
(previously "" meant unset->off; unset now means the 256k default).
- With `max_depth>=1` the engine owns a `SessionTreeSupervisor`;
embedders using the sync `close()` inside an event loop must switch to
`await engine.aclose()` (the idempotency test now pins `max_depth=0` for
that reason — it tests retries, not recursion).
- Tests: 130 passed; the 6 `test_acp.py` failures are pre-existing on
clean main in this environment.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Behavior changes for all deployments that relied on implicit defaults
(compaction off, no sub-agents); long runs may compact unexpectedly and
recursive `rlm()` is enabled unless env overrides are set.
> 
> **Overview**
> **Default execution policy** now turns on context compaction at
**256,000** tokens and allows **one level** of sub-agent recursion
(`max_depth=1`), including matching env defaults for `RLM_MAX_DEPTH`.
> 
> **`RLM_SUMMARIZE_AT_TOKENS` parsing** changes semantics: omitting the
variable applies the 256k threshold; **`""` or `"0"`** explicitly
disables compaction (replacing the old “unset/empty means off”
behavior). `_summarize_at_tokens` encodes that logic and invalid
positives still raise.
> 
> **Tests** assert the new defaults, cover disable-via-empty/zero,
switch async engine teardown from **`close()`** to **`await aclose()`**
(needed when recursion/supervisor is on), and pin **`max_depth=0`** on
the compaction/idempotency test so it stays focused on retries rather
than sub-agents.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
c37848a. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants