Skip to content

fix(agent): ignore Hermes INFO echoes that falsely fail completed runs (#5862) - #5864

Merged
Bohan-J merged 6 commits into
multica-ai:mainfrom
YikaJ:fix/hermes-sniffer-stderr-overmatch
Jul 24, 2026
Merged

fix(agent): ignore Hermes INFO echoes that falsely fail completed runs (#5862)#5864
Bohan-J merged 6 commits into
multica-ai:mainfrom
YikaJ:fix/hermes-sniffer-stderr-overmatch

Conversation

@YikaJ

@YikaJ YikaJ commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

What does this PR do?

Stops Multica from treating Hermes [INFO] root: conversation/tool echoes as provider failures.

Fixes #5862

Real error vs pollution error

This PR is specifically about pollution errors, not real Hermes/provider crashes.

Class Meaning Expected status Observed before fix
Real error Hermes/provider truly fails (rate limit, auth, AF_UNIX, timeout). No successful final reply. failed Correctly failed
Pollution error Hermes already finished and emitted a correct final reply / side effect; Multica then suddenly flips the same run to failed because non-error stderr data matched error-looking tokens. completed Incorrectly failed

Pollution is characterized by timing:

  1. run-messages already contains a successful final reply.
  2. the task then suddenly gets status=failed.
  3. the stored error is truncated conversation / tool / INFO JSON, not a short provider message.

Production evidence

  • Real failures are short and self-contained (41–95 bytes), e.g. AF_UNIX path too long, HTTP 400: ..., HTTP 429: ..., Request timed out.
  • Pollution failures are larger (265–87,286 bytes), contain conversation fingerprints (finish_reason, session_id, tool_call_id, [INFO] root:), and often start mid-JSON.
  • Sampled polluted runs had already completed their requested work and only failed at final status promotion.
  • The same pollution shape appears on at least two Hermes agents.
  • A length-only guard is insufficient: shortest polluted sample observed was 265 bytes.

Root cause

Hermes writes conversation/tool JSON to stderr as:

YYYY-MM-DD HH:MM:SS [INFO] root: {...}

Those INFO records are data. Their payload may contain Error:, KeyError:, , HTTP 429, or API call failed. Because one JSON record is one physical line, unrelated payload fragments can satisfy both the sniffer capture filter and terminal condition. After #2323 removed the empty-output precondition, that false terminal match overrides a valid completed turn.

Changes

  • Ignore Hermes [INFO] root: records before provider-error matching.
  • Keep acpMaxErrorLineLen as defense in depth for oversized echoes with an unexpected prefix.
  • Keep genuine [ERROR] root: / short provider-error behavior.
  • Add English-only regression cases covering four pollution shapes, plus a control case for real [ERROR] root: records.

How to test

cd server
go test ./pkg/agent/ -run 'TestHermesProviderErrorSniffer(IgnoresEchoedInfoRecords|StillCapturesErrorRootRecords)' -v
go test ./pkg/agent/ -run TestHermesProviderErrorSniffer
go test ./pkg/agent/
go vet ./pkg/agent/

Type of Change

  • Bug fix (non-breaking change that fixes an issue)

…oed stderr

Hermes echoes whole conversation / tool-result records to stderr at
`[INFO] root:` level as a single physical line tens of KB long. When
that echoed content embeds unrelated skill-doc substrings — a bare "❌"
bullet (matches acpTerminalErrorRe) and an "Error:"/"KeyError:" fragment
(matches acpErrorDetailRe) far apart on the same line — the per-line
sniffer treated the whole line as a terminal provider error and flipped
a completed run to failed, storing an 18-87 KB blob as the task error.
taskfailure.Classify then mislabeled it via bait substrings, yielding
spurious provider_auth_or_access / context_overflow reasons.

Cap the per-line length considered by the sniffer (acpMaxErrorLineLen).
Genuine ACP provider-error lines are short (~150 bytes); a conversation
echo is tens of KB, so an over-long line can never be a real error line.
This keeps echoed content out of the sniffer while still admitting every
real provider-error line.

Fixes multica#5862

Co-authored-by: Cursor <cursoragent@cursor.com>
@vercel

vercel Bot commented Jul 24, 2026

Copy link
Copy Markdown

@YikaJ is attempting to deploy a commit to the IndexLabs Team on Vercel.

A member of the Team first needs to authorize it.

Production samples show short and long conversation records can reach stderr through Python's root logger. Ignore those INFO records before provider-error matching, while retaining genuine ERROR records and the size bound as defense in depth.

Co-authored-by: Cursor <cursoragent@cursor.com>
@YikaJ YikaJ changed the title fix(agent): stop Hermes provider-error sniffer from over-matching echoed stderr (#5862) fix(agent): ignore Hermes INFO echoes that falsely fail completed runs (#5862) Jul 24, 2026
YikaJ and others added 4 commits July 24, 2026 11:40
Spell out that pollution failures already produced a successful final
reply and were only flipped to failed at promotion time, while genuine
ERROR root records remain terminal.

Co-authored-by: Cursor <cursoragent@cursor.com>
…g runs

Two boundary bugs behind the Hermes INFO over-match fix (multica-ai#5862):

- A 4096-byte pre-match drop discarded any long stderr line before error
  matching, so a genuine long provider failure (e.g. HTTP 400 with a large
  {'detail': ...}) plus empty output was promoted to completed — the multica-ai#1952
  regression. Length now only bounds the persisted summary, never whether a
  line is classified as an error.
- The [INFO] root: filter matched only the first physical line, so a
  multi-line INFO echo whose continuation JSON embedded a terminal token
  still flipped a completed run to failed. The sniffer now tracks Python
  root-logger records and skips INFO/DEBUG records plus their continuation
  lines, while ERROR/WARNING/CRITICAL and bare provider-error lines match.

Adds regression tests: multi-line INFO echo stays completed, a real
[ERROR] root record after an INFO echo stays terminal, and a long real
error with empty output still fails across all shared-sniffer providers.

Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
Co-authored-by: multica-agent <github@multica.ai>
@Bohan-J

Bohan-J commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Thanks @YikaJ for the excellent diagnosis and the initial fix here 🙏

The pollution-vs-real-error framing, the production evidence, and the deterministic regression fixtures made this bug much easier to reason about — that's a high-quality report.

Since this is a live-impacting bug, we pushed the remaining boundary fixes directly on top of your branch rather than round-tripping review, so we could land it quickly:

  • skip INFO/DEBUG root echoes via a structural JSON state machine (single- and multi-line records), instead of a prefix-only guard;
  • keep genuine bare provider errors (⚠️//📝 Error:) and non-root [ERROR] records, including when they follow a truncated INFO record;
  • bound only the persisted error summary by length, never the error classification.

CI is green and the review is clear — merging now. Really appreciate the contribution!

@Bohan-J
Bohan-J merged commit 2691110 into multica-ai:main Jul 24, 2026
6 checks passed
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.

[Bug]: Hermes ACP provider-error sniffer over-matches echoed stderr (INFO conversation logs) and flips completed runs to failed

2 participants