Files
multica/server/internal
740ebdb8ef MUL-5991 feat(agent): enable ACP thinking_level for the hermes provider (jcode) (#6814)
* feat(agent): enable ACP thinking_level for the hermes provider (jcode)

The reporter confirmed from jcode source (v0.71.1 and v0.73.0) that its
advertised effort is genuinely applied, not just echoed: session/set_config_option
waits for an `effort_changed` ack, and the provider request carries
`reasoning.effort` upstream. That was the one open question blocking this
half of GitHub #6720.

The wrinkle is that `hermes` is one provider type over two unrelated
binaries with opposite answers here:

  - jcode advertises option id `reasoning_effort` under category
    `thought_level` and applies it. The shared parser already matches on
    category, so the id being unrecognised costs nothing and the id we
    send back is jcode's own.
  - Hermes Agent has no effort surface on ACP at all. Re-verified against
    v0.20.0 today by driving `hermes acp` directly: session/new still
    returns only `_meta`, `models`, `modes`, `sessionId`, unchanged from
    the v0.18.2 finding in MUL-5770.

A version gate cannot separate those — they are different projects, not
different releases. The catalog can, and does: an empty catalog means no
picker and no set_config_option call, so admitting `hermes` to
acpCatalogThinkingProviders is safe for both. This is the case the
catalog-driven design was built for.

Also corrects the docs that stated hermes has no reasoning control. That
is now binary-specific rather than universally true, and Copilot takes
over as the example of a runtime with no effort dial at all.

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

* test(handler): retarget the no-reasoning-control fixture from hermes to copilot

These tests used hermes as the canonical runtime with no effort dial, so
enabling it broke them in CI — they passed locally only because the
handler suite skips without a database.

Copilot takes over the role: it discovers over ACP but executes through
its own CLI, so there is genuinely no live ACP session to carry an
effort. The behavior being pinned is unchanged — a capability gap must
not be reported as an unrecognised token.

Adds the inverse assertion in the same test: hermes now accepts a level
at the provider gate, since jcode applies one. Whether a picker appears
is settled per session by the discovered catalog, not here.

Verified against a real Postgres this time rather than the skip path.

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

* fix(agent): keep the capability 400 for hermes runtimes with no effort dial

Opening the thinking_level gate for the hermes provider regressed the
majority of that provider's users. Hermes Agent, not jcode, is what most
people run there, and it advertises no effort option — so a CLI
`--thinking-level high` went from an immediate, accurate 400 to a 201
followed by the daemon silently dropping the level at task time.

The provider name cannot distinguish the two binaries, but the model
catalog the daemon already reports can: a hermes runtime whose catalog
advertises no thinking levels is a Hermes Agent install. Consult it in
the create and update paths and restore the capability message for that
case, leaving jcode unaffected.

Fails open on purpose. A cold cache, an offline daemon, or a
never-discovered runtime all skip the check, so a valid jcode agent is
never blocked before its first discovery; the daemon's pre-execution
guard remains the backstop.

Verified against a real Postgres, including the fail-open paths.

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

* fix(agent): refuse a thinking level on an undiscovered hermes runtime

Review caught that the fail-open branch was not the transient cold start I
assumed. The model catalog is written only by ReportModelListResult, i.e.
only after a client explicitly asks for a model list, so a caller who works
through the CLI and never opens a model picker stays undiscovered
indefinitely. Reading "unknown" as "supported" therefore left the majority
Hermes Agent case exactly where it started: `--thinking-level high` accepted
and persisted, then silently dropped by the daemon at task time.

Replace the boolean with three states — present / absent / unknown — and
refuse on unknown, but only for providers whose name does not determine the
binary. `hermes` is the sole member; reasonix names one binary that does
support an effort, so an undiscovered reasonix runtime stays allowed rather
than being blocked before its first discovery.

Unknown gets its own message rather than reusing "does not support a
per-agent reasoning effort". That sentence would be actively false for a
jcode runtime, sending its owner to look for a limitation that isn't there;
naming the missing evidence points at what actually resolves it.

Also adds the execution-chain coverage the review asked for: jcode-shaped
ACP stubs prove set_config_option(reasoning_effort) goes out before
session/prompt on both session/new and session/resume, and a Hermes
Agent-shaped payload produces no config call at all. That is the substitute
evidence for having no real jcode to test against.

Skill and source map updated so the documented guarantee matches the code
rather than being stronger than it.

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

* fix(agent): word the undiscovered-runtime 400 for create as well as update

The message pointed at "the agent's model picker", which does not exist on
the create path — the agent is not created yet. "the model picker for this
runtime" is accurate on both paths (review nit, PR #6814).

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:42:47 +08:00
..