Skip to content

Add assembly cap guardrails - #4

Merged
stephenschoettler merged 3 commits into
stephenschoettler:mainfrom
Tosko4:feat/assembly-cap-guardrails
Apr 12, 2026
Merged

Add assembly cap guardrails#4
stephenschoettler merged 3 commits into
stephenschoettler:mainfrom
Tosko4:feat/assembly-cap-guardrails

Conversation

@Tosko4

@Tosko4 Tosko4 commented Apr 12, 2026

Copy link
Copy Markdown
Collaborator

This adds two optional guardrails for active-context assembly:

  • max_assembly_tokens: a hard cap for the assembled context
  • reserve_tokens_floor: keeps headroom inside the model context window

Why this helps:

  • it gives callers a simple way to avoid over-assembling context
  • it gets closer to a maxAssemblyTokenBudget style control without changing the existing default behavior
  • both knobs stay off unless explicitly configured

Tests:

  • added coverage for both guardrail modes
  • python3 -m pytest tests/test_lcm_engine.py tests/test_lcm_core.py -q

I kept this separate from the focus-topic change so each PR is easy to review on its own.

@Tosko4

Tosko4 commented Apr 12, 2026

Copy link
Copy Markdown
Collaborator Author

Quick heads up: I also have a Hermes side PR here: NousResearch/hermes-agent#8416

That PR is only about making Hermes work cleanly with external context engine plugins. It does not bundle or vendor hermes-lcm into Hermes.

I am linking it here because the guardrail knobs in this PR line up with the compatibility path on the Hermes side, especially around assembly and headroom behavior.

@stephenschoettler stephenschoettler left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Review: Request Changes

Good design with the two-knob approach (max_assembly_tokens + reserve_tokens_floor), but there's a correctness bug in tail trimming.

Bug — non-contiguous tail messages (engine.py ~line 476)

for msg in reversed(tail_messages):
    msg_tokens = count_message_tokens(msg)
    if used + tail_token_total + msg_tokens > assembly_cap:
        continue   # ← keeps scanning older messages
    kept_tail_reversed.append(msg)
    tail_token_total += msg_tokens

continue skips messages that don't fit but keeps iterating. If a large message doesn't fit but a smaller older message does, the older one is kept while the newer one is dropped — producing a non-contiguous conversation window with gaps. This should be break to maintain a contiguous recent-tail.

Minor notes

  1. tail_budget naming — This variable is the budget for summaries, not tail messages. Consider renaming to summary_budget.

  2. Silent guardrail disable — If reserve_tokens_floor >= context_length, the reserve cap computes to ≤ 0, gets filtered out, and the guardrail silently does nothing. A log warning for this misconfiguration would be helpful.

  3. Summary part selection — Same continue-vs-break concern applies to the summary selection loop (~line 508). Skipping a large summary but keeping a later one could produce incoherent context ordering.

  4. Test gap — Tests use uniform message sizes. Adding a test with varied sizes would expose the non-contiguous gap bug.

  5. Drive-by fix — The l2_budget_ratio and l3_truncate_tokens env-var bindings added to from_env look correct but are unrelated to this PR — worth noting in the description.

The core design is sound — just needs the continuebreak fix and a test for varied message sizes.

@Tosko4

Tosko4 commented Apr 12, 2026

Copy link
Copy Markdown
Collaborator Author

@stephenschoettler I pushed a follow-up for your review on this PR.

I fixed the contiguity issue you called out by switching the guardrail selection loops from skip-and-continue to stop-at-first-overflow behavior:

  • tail trimming now breaks on the first older message that doesn't fit, so the kept tail stays contiguous
  • summary part selection now does the same, so we don't skip a large summary and then keep a later smaller one

I also renamed tail_budget to summary_budget in that path, and added a warning when reserve_tokens_floor >= context_length so that misconfiguration doesn't fail silently.

Added regression coverage for:

  • varied-size tail messages that would have produced a gap before
  • varied-size summary parts that would have produced a non-contiguous summary set before
  • the reserve-floor warning case

Validation:

  • python3 -m pytest tests/test_lcm_engine.py tests/test_lcm_core.py -q
  • 51 passed

Latest commit on the PR branch: c1a5b2a

@Tosko4

Tosko4 commented Apr 12, 2026

Copy link
Copy Markdown
Collaborator Author

@stephenschoettler I pushed one more follow-up on top of c1a5b2a for a related guardrail edge case.

The contiguity fix in that commit was still correct, but it exposed another case:

  • if the newest tail message alone exceeds the assembly cap
  • _assemble_context() could end up dropping the entire tail

This follow-up keeps the newest tail message even when it alone exceeds the cap, while still preserving contiguous stop-at-first-overflow behavior for older tail messages.

Added regression coverage for:

  • newest tail message is preserved when it alone exceeds max_assembly_tokens

Validation:

  • python3 -m pytest tests/test_lcm_engine.py::TestAssemblyGuardrails::test_max_assembly_tokens_keeps_newest_tail_message_even_if_it_alone_exceeds_cap -q
  • python3 -m pytest tests/test_lcm_engine.py tests/test_lcm_core.py -q
  • 52 passed

Latest commit on the PR branch: 1afa744

@stephenschoettler stephenschoettler left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Review: Approve (updated)

Both issues from my previous review are fixed:

  • continuebreak in tail trimming — now produces contiguous recent tail
  • continuebreak in summary selection — stops on first overflow
  • Renamed tail_budgetsummary_budget

The and kept_tail_reversed guard on the break condition is a nice touch — allows a single oversized message to still be included if nothing else has been selected yet.

CI green across 3.11-3.13. LGTM.

@stephenschoettler
stephenschoettler merged commit 0d5b373 into stephenschoettler:main Apr 12, 2026
3 checks passed
@Tosko4
Tosko4 deleted the feat/assembly-cap-guardrails branch April 12, 2026 23:11
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