Skip to content

fix(prompt): append the tool-policy boundary instead of prepending it (#5704) - #5821

Open
ntdatt812 wants to merge 1 commit into
tinyhumansai:mainfrom
ntdatt812:fix/5704-tool-policy-boundary-placement
Open

fix(prompt): append the tool-policy boundary instead of prepending it (#5704)#5821
ntdatt812 wants to merge 1 commit into
tinyhumansai:mainfrom
ntdatt812:fix/5704-tool-policy-boundary-placement

Conversation

@ntdatt812

@ntdatt812 ntdatt812 commented Aug 27, 2026

Copy link
Copy Markdown

Closes #5704.

build_system_prompt put ## Tool Policy Boundary in front of the whole assembled prompt:

prompt = format!("{boundary}\n\n{prompt}");

Every line of that block is session-scoped — agent id, channel, entry point, risk level, allowed permission, the allowed-tool list, a restricted count. Putting it first moves the prompt's first diverging byte to offset 0, so two turns share no cached prefix at all even when the safety preamble, tool catalogue and workspace sections behind it are byte-identical. It renders whenever anything is blocked or hidden, which is the normal state for any scoped toolbelt.

This is the same concern the surrounding code already goes out of its way to protect:

  • SystemPromptBuilder::for_subagent deliberately excludes DateTimeSection"Repeat spawns of the same sub-agent definition must produce byte-identical system prompts so the inference backend's automatic prefix cache can reuse the prefill".
  • connections::connected_overview is sorted "so a prompt built from this does not reshuffle between turns and cost its cached prefix".

One varying value was worth excluding; seven varying values ahead of everything were not caught.

Appending loses nothing — the model reads the whole system message either way — and it restores the archetype/persona as the opening line, which the prepend had replaced with the same constant heading for every agent.

Testability

The placement lived inside a session method, so I pulled the composition into a pure helper:

fn append_tool_policy_boundary(prompt: String, boundary: Option<String>) -> String

Everything that decides the ordering is now in those two arguments, so it can be tested without standing up a session.

Tests

Four, three of which fail against the prepend (verified by reverting the helper: 1 passed; 3 failed):

test prepend append
the boundary lands after the prompt body
the persona stays the opening line
two agents differing only in the boundary share the whole body as a common leading prefix
no boundary leaves the prompt untouched

The third asserts the cache property directly rather than through a proxy — it walks the two prompts byte-by-byte and requires the shared prefix to cover the entire stable body. That is the thing the issue is actually about, so I wanted the test to measure it rather than to assert an ordering that merely implies it.

The fourth is included deliberately and passes either way; it keeps the set honest about what this change does not alter.

Verification

cargo test --lib turn::context — 4 passed. cargo fmt --all — clean.

Summary by CodeRabbit

  • Bug Fixes
    • Improved system prompt construction so tool-use guidance appears in the correct position.
    • Preserved existing prompt content and persona details when tool guidance is applied.
    • Ensured prompts remain unchanged when no tool-use guidance is configured.

…tinyhumansai#5704)

`build_system_prompt` put the `## Tool Policy Boundary` block in front of the
whole assembled prompt. Every line of that block is session-scoped — agent id,
channel, entry point, risk level, allowed permission, the allowed-tool list,
a restricted count — so the prompt's first diverging byte sat at offset 0 and
the inference backend's automatic prefix cache had nothing to reuse behind it,
even when the safety preamble, tool catalogue and workspace sections were
byte-identical.

That contradicts what the surrounding code already protects:
`SystemPromptBuilder::for_subagent` keeps `DateTimeSection` out precisely to
avoid one varying value inside the prompt, and the connected-server overview is
sorted so it "does not reshuffle between turns and cost its cached prefix".

It also restores the archetype/persona as the prompt's opening line. The
prepend replaced it with the same constant heading for every agent, which is a
silent layout change for anything that identified an agent that way.

The composition moved into a pure `append_tool_policy_boundary(prompt,
boundary)` so the ordering is testable without standing up a session.

Four tests, three of which fail against the prepend:

  - the boundary lands after the prompt body
  - the persona stays the opening line
  - two agents differing only in the boundary share the whole body as a
    common leading prefix (this is the cache property itself, not a proxy)
  - no boundary leaves the prompt untouched (passes either way)

`cargo test --lib turn::context` 4 passed; reverting the helper to prepend
gives 1 passed / 3 failed. `cargo fmt --all` clean.
@ntdatt812
ntdatt812 requested a review from a team August 27, 2026 08:33

@tinysweeper tinysweeper 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.

tinysweeper found nothing blocking. Approving.

$0.0000 · 0 in / 0 out

@tinysweeper

tinysweeper Bot commented Aug 27, 2026

Copy link
Copy Markdown

How this change flows

0 changed behaviours across 4 relationships. 6 surrounding behaviours are shown (60 graph nodes walked). 38 further behaviours left out to keep the diagram readable.

flowchart LR
  n0["fetch_learned_context"]:::impacted
  n1["build_system_prompt"]:::impacted
  n2["bound_cached_transcript_messages"]:::impacted
  n3["trim_history"]:::impacted
  n4["extend"]:::impacted
  n5["openhuman"]:::impacted
  n0 -->|uses| n5
  n1 -->|uses| n5
  n2 -->|calls| n4
  n3 -->|calls| n4
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading

Green: changed behaviour. Grey: surrounding behaviour. Arrows name the call, use, implementation, or test relationship. Orange: has findings. Red: has a finding that blocks the merge.

tinysweeper 0.1.0

@tinysweeper tinysweeper Bot added the priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. label Aug 27, 2026
@coderabbitai

coderabbitai Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 72f6f3eb-a909-427a-b6a8-19b80941342a

📥 Commits

Reviewing files that changed from the base of the PR and between 04075d5 and b1e97d6.

📒 Files selected for processing (1)
  • src/openhuman/agent/harness/session/turn/context.rs

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The system prompt builder now appends the optional tool-policy boundary after the assembled prompt. A helper handles absent boundaries. Tests verify ordering, persona placement, shared prompt prefixes, and unchanged output without a boundary.

Changes

System prompt boundary placement

Layer / File(s) Summary
Append boundary and validate prompt output
src/openhuman/agent/harness/session/turn/context.rs
build_system_prompt appends the rendered tool-policy boundary. Tests verify ordering, persona preservation, shared-prefix behavior, and the no-boundary case.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to b1e97

This localized change appends the session-specific tool-policy boundary so stable prompt content can retain a shared prefix while preserving the same policy information. No actionable merge-blocking risk remains beyond normal checks and review.

Suggested reviewers: senamakel

Poem

A rabbit watched the prompt line grow,
The persona stayed first in the flow.
The boundary hopped behind,
Stable prefixes aligned.
“No boundary?” Clean text will show!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary change: appending the tool-policy boundary instead of prepending it.
Linked Issues check ✅ Passed The changes satisfy issue #5704. The tool-policy boundary is appended after the assembled prompt, preserving the stable leading prefix and the persona as the opening line. Tests cover boundary orderin…
Out of Scope Changes check ✅ Passed The changes remain within scope for issue #5704. The helper extraction and focused tests directly support the prompt-placement fix and do not introduce unrelated behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 1 files.
Full details: Linked Issues check

Explanation

The changes satisfy issue #5704. The tool-policy boundary is appended after the assembled prompt, preserving the stable leading prefix and the persona as the opening line. Tests cover boundary ordering, shared-prefix behavior, persona preservation, and the no-boundary case.

Warning

Your free Security trial is over. An organization admin can activate Security or dismiss this notice.


Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tool Policy Boundary is prepended to the system prompt, putting session-varying content ahead of the cacheable prefix

1 participant