#2536

Hermes: using-superpowers bootstrap is injected into delegate_task subagents (Hermes counterpart of #2160)

Openthealps01-netizenopened 14d ago
  • I searched existing issues and this is not a duplicate. #2160 reports the same class for OpenCode; I found no Hermes report (searched "hermes subagent", "subagent bootstrap", "SUBAGENT-STOP"). #2223, the OpenCode fix, was closed unmerged and does not touch .hermes-plugin/.

Environment (required)

FieldValue
Superpowers versionv7.0.0, bb92a77741419a4ab5f06e711a283343f1ada0c3 (git-clone plugin install, tracking main)
HarnessHermes Agent desktop, native plugin (.hermes-plugin/)
Harness versionHermes Agent v0.22.0 (2026.9.24), e06df9c2
Your model + versionclaude-opus-5-5. This report was drafted by the agent from source reading and a zero-token probe; a human reviewed it before posting.
All plugins installedsuperpowers, honey (plus local plugins that register no pre_llm_call hook)
OS + shellWindows 11, Git Bash (MSYS)

Is this a Superpowers issue or a platform issue?

  • I confirmed this issue does not occur without Superpowers installed. The injected text is the plugin's own bootstrap and is the only pre_llm_call result returned.

The injection comes from .hermes-plugin/__init__.py. Hermes runs every pre_llm_call hook for child agents too; the only skip is _persist_disabled (agent/turn_context.py:801), which delegate_task children do not set.

What happened?

pre_llm_call returns the bootstrap whenever is_first_turn is true (.hermes-plugin/__init__.py:121). A delegate_task child starts with no history, so on its first turn it receives the full <EXTREMELY_IMPORTANT> bootstrap, byte-identical to the one the parent gets. The bootstrap then relies on <SUBAGENT-STOP> for the child to ignore it. #2160 shows that is not reliable: an OpenCode worker invoked brainstorming and restarted the design cycle instead of doing its bounded task.

Hermes already tells the hook that the caller is a child: delegate_task builds children with platform="subagent" (tools/delegate_tool.py:277), the agent keeps it (AIAgent(platform="subagent").platform == "subagent"), and _collect_pre_llm_call_context forwards it as platform=.

Steps to reproduce

No model call is needed. From the Hermes source checkout, with its own interpreter:

import types
from agent.turn_context import _collect_pre_llm_call_context
from agent.delegation_context import delegated_child_context

def stub(platform):
    return types.SimpleNamespace(session_id="probe", model="m", platform=platform,
                                 _parent_session_id="", _user_id="", _persist_disabled=False)

kw = dict(effective_task_id="t", turn_id="1", original_user_message="hi",
          messages=[{"role": "user", "content": "hi"}])
parent = _collect_pre_llm_call_context(stub("desktop"), conversation_history=None, **kw)
with delegated_child_context("probe-child"):
    child = _collect_pre_llm_call_context(stub("subagent"), conversation_history=None, **kw)
    later = _collect_pre_llm_call_context(stub("subagent"), conversation_history=[{"role": "user", "content": "x"}], **kw)
print(len(parent), len(child), len(later), parent == child)

Expected behavior

A delegate_task child gets no bootstrap: the parent has already chosen the process, and the child's brief carries what it needs.

Actual behavior

5729 5729 0 True

The parent and the subagent first turn both get 5,729 characters (roughly 1,400 tokens) of identical bootstrap. Later child turns get nothing.

Not verified on Hermes: whether a child actually ignores <SUBAGENT-STOP>. The behavioural failure is the one #2160 reports for OpenCode; I did not run a live delegation for this report.

Suggested fix

        if is_first_turn and platform != "subagent":
            return {"context": bootstrap}
        return None

Checked on a scratch copy of the adapter with this one-line change: a subagent first turn now gets nothing, while desktop and cron first turns still get the full bootstrap and later turns still get nothing.

platform is the explicit signal. parent_session_id does not mean "subagent" by itself: Hermes also records a parent for the continuation session it creates on context compression (agent/conversation_compression.py, publish_compression_child(parent_session_id=...)).

Debug log or conversation transcript

Injected context is not persisted in Hermes' session records, so a transcript would not show it. The probe above is the reproduction.

Activity

2
markalexwatson12d ago

Adding live Hermes delegation observations to this report. The existing reproduction establishes injection without a model call; my runs also show children acting on Superpowers-related instructions before starting their assigned work.

In three instrumented document-review delegations using a local model, the child's first tool action was:

  • Run 1: skill_view(superpowers:using-superpowers)
  • Run 2: skill_view(using-superpowers) → "not found", followed by skills_list and a qualified load
  • Run 3: a direct read_file call on a Superpowers SKILL.md, despite the skill being excluded from the child's discovery index

A captured outgoing request from a child session identified as source=subagent contained the bootstrap appended to its user message (5,707 UTF-8 bytes at the revision below).

The bootstrap includes both the subagent exemption (<SUBAGENT-STOP>) and an instruction not to reload using-superpowers. These runs nevertheless began with skill-loading attempts. It also supplies a direct-file fallback (read_file on the SKILL.md path), providing another route to the skill content when discovery or named loading is restricted.

Run 3 was performed while locally testing NousResearch/hermes-agent#140566 (associated with NousResearch/hermes-agent#140537, still an open draft at time of writing). That work addresses platform-specific discovery and skills-tool filtering; bootstrap injection is a separate path.

These observations support the guard already proposed in #2540. The runs incurred substantial elapsed time on local inference, but I have not isolated the bootstrap's contribution with a controlled before/after comparison.

Environment: superpowers bb92a777 (Hermes adapter, .hermes-plugin/__init__.py); hermes-agent 0.22.2 parent (d923a5eb) driving delegate children against a worktree at hermes-agent PR #140566 head (e2303218d1 by the final run); child model gpt-oss:20b (MXFP4 as shipped) via Ollama 0.40.5, CPU-only, 32k context.

Disclosure per contribution guidelines: prepared with assistance from Claude (Fable 5), DeepSeek-v4.1-flash (as the Hermes orchestrator running the instrumented delegations), and GPT-6 Astra (adversarial review, which found this existing issue and corrected an earlier draft).

arittr9d ago

Hi @thealps01-netizen — I'm Claude, an AI agent (Claude Opus 5.5, running in Claude Code 2.1.305), posting from Drew (@arittr)'s account at their direction. Drew reviewed the finding below and approved this reply.

Confirmed: the Hermes adapter's pre_llm_call hook returns the full using-superpowers bootstrap on every first turn, and a delegate_task child starts with no history, so its first turn gets the same bootstrap as the parent session. Running the hook from the current release with the arguments Hermes passes gives the parent and a subagent first turn identical context. We already stopped injecting the bootstrap into OpenCode task subagents (#2160), and the Hermes side is queued with us. Thanks for the report, and thanks @markalexwatson for the delegation traces.


If any of the evidence above is wrong, reply here — Drew reads these, and decisions can be revisited.

— Claude Opus 5.5, Claude Code 2.1.305, session 5d9d84c2-4219-41be-8d07-d9ba2ad854be