Files
multica/server/pkg/taskfailure
85045228a1 MUL-6102: fix(daemon): surface which HERMES_HOME a Hermes task actually read (#6892)
* fix(daemon): surface which HERMES_HOME a Hermes task actually read

A Hermes task runs against a per-task HERMES_HOME overlay, seeded from a
source home the daemon resolves from its own process environment. When
that resolution lands on a home holding no config.yaml, the overlay is
built anyway from nothing: the derived config keeps only
skills.external_dirs, so the user's provider and credentials are gone and
Hermes refuses every session/new with "No LLM provider configured".

Nothing on that path said so. The overlay was seeded silently, no line
logged the resolved source home, and the failure surfaced Hermes' own
remedy -- run `hermes model` -- which edits whichever home the user's
shell points at, not the one the task read. GH #6872 is what that costs:
eight documented workarounds, none of which could have worked, and a root
cause filed against an unrelated resume bug.

Three additions, no behaviour change:

- Warn when the source home is missing, or exists without a config.yaml.
  Kept a warning rather than a hard failure because config-less is a
  legitimate setup: an image that ships Hermes and injects OPENAI_API_KEY
  through the environment resolves a provider fine.
- Log the resolved source home once per task, at Info.
- Append the overlay and source homes to a "no LLM provider configured"
  failure, so the task itself names the directory that was read.

The annotation runs after every classifier has read the error text. It
embeds two user-chosen paths and Classify matches substrings anywhere, so
annotating earlier would let a directory name pick the failure_reason --
a home under /srv/token-cache/limit/ satisfies rule 1 (context overflow),
which is evaluated before rule 2 (missing config) and feeds retry
eligibility and resume blacklisting.

The phrase rule 2 already matched is now a shared const so the new
predicate and the classifier cannot drift apart.

Co-authored-by: multica-agent <[email protected]>

* fix(daemon): keep the Hermes hint out of resume-guard input

Review caught that the annotation did not stop at the daemon. It travels
to the server as the task's error, is persisted in
agent_task_queue.error, and is re-scanned there for the life of the row
by service.ResumeUnsafeFailure and by the ILIKE/regex guards in
GetLastTaskSession / GetLastChatTaskSession.

Both interpolated paths are user-controlled -- HERMES_HOME comes from the
agent's custom_env, the overlay root from MULTICA_WORKSPACES_ROOT -- so a
legal directory name got a vote on session recovery. Measured, with a
source home under /srv/400-invalid_request_error/:

  raw error                  ResumeUnsafeFailure = false
  annotated with the paths   ResumeUnsafeFailure = true
  annotated with this hint   ResumeUnsafeFailure = false

That true is a healthy chat session pointer being cleared on a failure
that has nothing to do with it, so "no resume semantics change" was not
true as written.

The hint is now fixed prose with nothing interpolated, and names the
daemon log line that does carry the paths. This removes the vector rather
than containing it: no wire or schema change, and it is correct against
old servers too, so there is no mixed-version window where an un-upgraded
server misreads a new daemon's text.

Tests cover the boundary the previous ones missed: classification AND
service.ResumeUnsafeFailure agree with the unannotated text across five
real failure shapes, plus a check against the SQL guard phrases, which no
unit test can execute.

Also softens the missing-config warning, which asserted the task runs
without credentials when the environment may well supply them -- the
config-less-but-working setup this PR deliberately keeps supported.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: Bohan-J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-08-13 16:05:06 +08:00
..