Skip to content

feat: pre-call sanitization and post-call tool guardrails#1732

Merged
teknium1 merged 1 commit intomainfrom
hermes/hermes-c91521bf
Mar 17, 2026
Merged

feat: pre-call sanitization and post-call tool guardrails#1732
teknium1 merged 1 commit intomainfrom
hermes/hermes-c91521bf

Conversation

@teknium1
Copy link
Copy Markdown
Contributor

Summary

Salvage of PR #1321 by @alireza78a — reimplemented against current main.

Phase 1 — Pre-call message sanitization

_sanitize_api_messages() now runs unconditionally before every LLM call. Previously gated on context_compressor being present (line 4998), so sessions loaded from disk or running without compression could silently accumulate dangling tool_call/tool_result pairs — causing "No tool call found for call_id" API errors.

Phase 2a — Delegate task cap

_cap_delegate_task_calls() truncates excess delegate_task calls per turn to MAX_CONCURRENT_CHILDREN. The existing cap in delegate_tool.py only limits the task array within a single call; this catches multiple separate delegate_task tool_calls in one turn.

Phase 2b — Tool call deduplication

_deduplicate_tool_calls() drops duplicate (tool_name, arguments) pairs within a single turn when models stutter.

All three are static methods on AIAgent, independently testable.

Tests

29 tests in tests/test_agent_guardrails.py covering all three phases — orphaned result removal, stub injection, mixed orphans, delegate cap with interleaved ordering, dedup first-occurrence preservation, input mutation safety, empty list edge cases, SDK object vs dict format handling.

Closes #626

Salvage of PR #1321 by @alireza78a (cherry-picked concept, reimplemented
against current main).

Phase 1 — Pre-call message sanitization:
  _sanitize_api_messages() now runs unconditionally before every LLM call.
  Previously gated on context_compressor being present, so sessions loaded
  from disk or running without compression could accumulate dangling
  tool_call/tool_result pairs causing API errors.

Phase 2a — Delegate task cap:
  _cap_delegate_task_calls() truncates excess delegate_task calls per turn
  to MAX_CONCURRENT_CHILDREN. The existing cap in delegate_tool.py only
  limits the task array within a single call; this catches multiple
  separate delegate_task tool_calls in one turn.

Phase 2b — Tool call deduplication:
  _deduplicate_tool_calls() drops duplicate (tool_name, arguments) pairs
  within a single turn when models stutter.

All three are static methods on AIAgent, independently testable.
29 tests covering happy paths and edge cases.
@teknium1 teknium1 merged commit 85993fb into main Mar 17, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant