Skip to content

feat(agent): declarative per-fragment system-prompt overrides - #44610

Open
adambiggs wants to merge 1 commit into
NousResearch:mainfrom
adambiggs:feat/prompt-fragment-overrides
Open

feat(agent): declarative per-fragment system-prompt overrides#44610
adambiggs wants to merge 1 commit into
NousResearch:mainfrom
adambiggs:feat/prompt-fragment-overrides

Conversation

@adambiggs

Copy link
Copy Markdown
Contributor

What & why

Adds agent.prompt_overrides — a config surface to reshape any named stable-tier system-prompt fragment (replace / append / prepend / remove) without editing source.

Today the built-in guidance blocks are either hardcoded or gated by bespoke all-or-nothing booleans (task_completion_guidance, tool_use_enforcement, environment_probe). That only lets you delete a whole block, never reshape it — so the common need to qualify a block (e.g. append a "stop and report on environment friction instead of working around it" caveat to the "Finishing the job" guidance) has no clean path short of forking. This generalizes those one-off toggles into a uniform, discoverable override map and gives users real context-engineering control.

Design: pure data, cache-safe by construction

Every stable-tier fragment now carries a stable key and is emitted through a single override-aware emit() choke point in build_system_prompt_parts. Overrides are pure data resolved once at build time — no callable hook, no conditional logic, by design. The assembled prompt stays a deterministic function of (agent, config), so it remains byte-stable across turns and the provider prefix cache stays warm. With no overrides configured the prompt is byte-identical to the default (covered by a regression test). An override only applies when the fragment is actually emitted this session; append/prepend to an absent fragment is a no-op.

Scope is intentionally the stable tier (Hermes-authored guidance/identity). Context-tier (project files, system message) and volatile-tier (memory, timestamp) are user/session data, not prompt fragments; emit() is tier-agnostic so extending later is trivial.

Example

agent:
  prompt_overrides:
    task_completion:
      mode: append
      text: >-
        Exception — environment friction: if a tool, filesystem, dependency,
        credential, integration, or environment problem blocks the real path,
        STOP immediately, report the exact failure, and wait for the user to
        fix it at the source. Do NOT work around it unless the user approves.
    google_operational: { mode: remove }
    steer_channel: "Use the steer channel only for urgent corrections."  # bare string = replace

Fragment keys (16, stable tier): identity, hermes_help, task_completion, tool_guidance, steer_channel, computer_use, nous_subscription, tool_use_enforcement, google_operational, execution_discipline, skills, model_identity, environment_hints, environment_probe, active_profile, platform_hints. Canonical registry with descriptions lives in agent/prompt_overrides.py (FRAGMENT_KEYS).

Existing on/off toggles are untouched and still work.

How to test

scripts/run_tests.sh tests/agent/test_prompt_overrides.py        # 25 engine unit tests
scripts/run_tests.sh tests/run_agent/test_run_agent.py           # 383, incl. 6 new integration tests
scripts/run_tests.sh tests/agent/test_prompt_builder.py          # 130, assembly regression
  • test_no_overrides_byte_identical asserts the cache-warmth guarantee (empty overrides → identical prompt).
  • Integration tests exercise replace / append / remove / bare-string-shorthand / unknown-key-ignored through the full _build_system_prompt() path.

Manual:

# ~/.hermes/config.yaml
agent:
  prompt_overrides:
    task_completion: { mode: remove }

…then hermes chat and confirm the "Finishing the job" block is gone (and present again without the override).

Platforms

Pure-Python config plumbing, no OS-specific code paths. Developed/tested on Linux.

Files

  • agent/prompt_overrides.py (new) — registry + normalize_overrides + apply_fragment_override
  • agent/system_prompt.py — 16 stable-tier append sites routed through emit()
  • agent/agent_init.py — load + normalize config
  • hermes_cli/config.py, cli-config.yaml.example — default + worked example
  • website/docs/developer-guide/prompt-assembly.md — override surface docs
  • tests — 25 unit + 6 integration

🤖 Generated with Claude Code

@alt-glitch alt-glitch added type/feature New feature or request comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint area/config Config system, migrations, profiles P3 Low — cosmetic, nice to have labels Jun 15, 2026
@adambiggs
adambiggs force-pushed the feat/prompt-fragment-overrides branch from 0c9867d to 567827e Compare July 2, 2026 00:11
Add agent.prompt_overrides — a config surface to reshape any named
stable-tier system-prompt fragment (replace/append/prepend/remove)
without editing source. Gives full context-engineering control over
Hermes's built-in guidance blocks (e.g. qualifying the "Finishing the
job" / "Tool-use enforcement" persistence rules) instead of the prior
all-or-nothing on/off toggles.

Every stable-tier fragment now carries a stable key and is emitted
through a single override-aware `emit()` choke point in
build_system_prompt_parts. Overrides are pure data resolved once at
build time — no callable hook, no conditional logic — so the assembled
prompt stays a deterministic function of (agent, config) and the
provider prefix cache stays warm. With no overrides the prompt is
byte-identical to the default (covered by test).

- agent/prompt_overrides.py: FRAGMENT_KEYS registry (16 keys),
  normalize_overrides (validation + bare-string shorthand),
  apply_fragment_override (mode resolution)
- agent/system_prompt.py: route all 16 stable-tier append sites
  through emit()
- agent/agent_init.py: load+normalize agent.prompt_overrides
- config default, example config with worked stop-on-friction case,
  developer-guide docs
- 25 engine unit tests + 6 end-to-end integration tests

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@adambiggs
adambiggs force-pushed the feat/prompt-fragment-overrides branch from 567827e to d100007 Compare July 9, 2026 07:36

@teknium1 teknium1 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the focused, cache-aware configuration surface. The underlying capability remains absent on current main, and #48101 is complementary rather than a replacement.

Problems

  • The claimed stable-tier coverage is incomplete: PR commit d1000077511a still has stable_parts.append(PARALLEL_TOOL_CALL_GUIDANCE) at agent/system_prompt.py:230, but FRAGMENT_KEYS at agent/prompt_overrides.py:43-60 has no corresponding key. parallel_tool_call_guidance will be ignored as unknown.
  • The same branch retains coding_system_blocks() through stable_parts.extend() at agent/system_prompt.py:364-374. Please either model its supported override granularity or narrow the “every stable-tier fragment” claims in code and docs.
  • tests/agent/test_prompt_overrides.py:111 fixes the registry count at 16. This is a change-detector test and will not protect the coverage contract; it already permits the omitted parallel block.

Suggested changes

  • Route parallel guidance through emit() and test a real configured override; decide and document the coding-block scope.
  • Replace the fixed-count assertion with behavioral coverage of supported fragments.

Automated hermes-sweeper review.

Comment thread agent/prompt_overrides.py
# with the ``emit(...)`` call sites in ``agent/system_prompt.py``. Surfaced to
# users for discovery (``hermes`` docs / tooling) — an override map keyed by
# string is only usable if the keys are enumerable.
FRAGMENT_KEYS: Dict[str, str] = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This registry is documented as canonical, but agent/system_prompt.py:230 in this PR still directly appends PARALLEL_TOOL_CALL_GUIDANCE. Add a key and route that block through emit(), or narrow the feature/docs claim so the omission is explicit.


class TestFragmentKeyRegistry:
def test_keys_present_and_documented(self):
assert len(FRAGMENT_KEYS) == 16

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Please remove the fixed registry-count assertion. It is a change-detector test and does not enforce the useful contract; behavioral coverage for the parallel-guidance override would have caught the currently omitted stable block.

@teknium1 teknium1 added sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit labels Jul 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/config Config system, migrations, profiles comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint P3 Low — cosmetic, nice to have sweeper:blast-broad Sweeper blast radius: broad — a core path most sessions hit sweeper:risk-caching Sweeper risk: may break/degrade prompt caching or cache-key stability (invariant) type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants