3df7c38ed1 fix(agent): normalise the provider prefix in the Hermes set_model skip-gate (MUL-6958) (#7908)
* fix(agent): normalise the provider prefix in the Hermes set_model skip-gate

The gate added in #5690 (MUL-5029) compares the configured model against the
model the session reports as current with a raw `==`:

    if opts.Model != "" && effectiveModel == sessionCurrentModel {

Those two values come from different id namespaces, so for an agent whose
`model` is stored bare the comparison can never be true. The gate is dead code
for those agents and `session/set_model` is replayed on every turn — which is
the exact situation #5690 was opened to prevent.

Why the two sides cannot match:

  - Hermes always reports a provider-encoded id. `_encode_model_choice` in its
    ACP adapter returns `f"{provider}:{model}"` whenever a provider resolves,
    and the provider falls back to `detect_provider()` or `"openrouter"`, so it
    is effectively never absent. Confirmed against a live `hermes acp` session:
    session/new populates `models.currentModelId` from that encoder, so a bare
    configured `claude-sonnet-4-5` on a `custom` provider is reported back as
    `custom:claude-sonnet-4-5`.

  - `agent.model` is persisted verbatim. `handler/agent.go` writes `req.Model`
    straight into the column on create and update; nothing normalises or
    validates a `provider:` prefix on that path.

  - The bare spelling is a documented, first-class input. `cmd_agent.go`'s own
    flag help is `--model  Model identifier (e.g. claude-sonnet-4-6,
    openai/gpt-4o)`. Only the ACP-catalog dropdown produces the prefixed form,
    because `acpModelEntry` uses the advertised id as `Model.ID`.

So dropdown-configured agents match and skip; every agent configured through
the CLI, the API, a seed, or a row predating the catalog does not, and never
will.

What the replay costs, per turn, in the Hermes runtime: `set_session_model`
replaces the session's agent with `_make_agent`, which re-runs `load_config` and
`resolve_runtime_provider`, joins background MCP discovery for up to
`mcp_discovery_timeout` (~1.5s), and constructs a fresh agent whose system
prompt is then re-derived from disk. It also re-runs `_resolve_model_selection`,
i.e. the provider auto-detection this gate exists to avoid — so the
`custom:deepseek-v4-pro` mis-route from #5690 is still live for precisely the
agents that hit it. Running Hermes' own resolver over the ids this daemon sends
reproduces it: `custom` + `deepseek-v4-pro` resolves to `openrouter`, and
`anthropic` + a bare Claude id resolves away from `anthropic`, both of which
make `_make_agent` drop base_url and api_mode.

The fix replaces the raw comparison with `acpModelIDsEquivalent`, which
normalises the optional prefix before comparing:

  - both sides carry an explicit provider → compare the whole id, so a genuine
    provider switch (openrouter:X → custom:X) still sends set_model;
  - exactly one side carries a provider → compare model names only; a bare
    configured id expresses no provider preference, so the session's own
    provider is authoritative;
  - either side empty → not equivalent, preserving the existing fall-through
    for runtimes that report no current model.

Only the first colon separates provider from model, so slash-bearing
OpenRouter-style names (`nous:moonshotai/kimi-k2.6`) split correctly, and the
comparison is case-insensitive because Hermes lowercases the provider when it
encodes the id.

No migration and no operator action. Rows already written in the prefixed form
keep working — both spellings now resolve to the same selection.

Deliberately not argued on cost. A cache-hit-rate difference first pointed here,
but that measurement was grouped by "does the agent have a configured model",
which on the measured deployment was confounded with the transport wire, so this
change does not claim it. It stands on the comparison being unsatisfiable and on
the guard it restores.

Tests: 12 `acpModelIDsEquivalent` cases, including the provider-switch and
different-model negatives, the case-folding and whitespace cases, and the
slash-bearing model id; plus 6 `splitACPModelID` cases covering the
leading-colon and multi-colon shapes.

* fix(agent): only skip set_model when an explicit provider is confirmed

Review caught a regression introduced by the previous commit: the third
equivalence rule was symmetric, but the risk is not.

    acpModelIDsEquivalent("openrouter:hermes-4", "hermes-4") // => true

Comparing model names whenever *either* side lacked a provider meant a caller
who named a provider explicitly got the switch swallowed, silently, and the
session kept whatever provider it had defaulted to. `origin/main` sends
set_model for that input, so the previous commit changed behaviour for it.

The premise that justified the symmetry — "Hermes always reports a
provider-encoded id" — is false, and this repository already contained the
counterexamples: `acp_effort_test.go` captures `currentModelId: "hermes-4"`
from `hermes acp` v0.20.0 and `currentModelId: "gpt-5.6-sol"` from jcode, both
unprefixed, and jcode routes through this same backend (hermes.go states it
covers both binaries). `_encode_model_choice` returning a prefix is what
happens when a provider resolves; it is not a property of every input.

The rule is now asymmetric:

  - bare configured id  -> equivalent on the model name alone. It expresses no
    provider preference, so the session's provider is authoritative. This is
    the case the gate exists for, and it still skips.
  - explicit configured provider -> equivalent only when the session reports
    that same provider. A bare current id cannot confirm the match, so we fall
    through and send set_model, as before this change.

Tests:

  - three `acpModelIDsEquivalent` table entries, including the missing
    `provider:model` vs bare-id row that let the regression through;
  - `TestHermesSkipsRedundantSetModelForBareConfiguredModel`, an end-to-end
    frame-recording test that asserts the gate actually fires for the bare
    spelling — the unit tests never checked that set_model stopped going out;
  - `TestHermesSendsSetModelWhenExplicitProviderAndBareCurrent`, which fails
    without the asymmetric rule (verified: the recorded frame is absent).

Also addressed from the same review:

  - dropped the stale cache-hit-rate paragraph from the `acpModelIDsEquivalent`
    comment. The PR description claimed it had been removed; it had not.
  - dropped the paragraph arguing the benefit via MCP refresh and tool-cache
    prefix invalidation. It described Hermes' Python internals this repository
    cannot verify or track, and it read as arguing against the sentence right
    after it.
  - softened "the comparison can never be true": it holds when the runtime
    reports a prefixed id, which is not always.
  - noted that `splitACPModelID` cannot distinguish a provider prefix from a
    colon inside a bare model name (`llama3:8b`), and that the degradation is
    safe in the only direction that matters — it falls back to sending
    set_model, never to a false skip.

* docs(agent): correct the bare-current-id evidence in the set_model gate

The justification for the asymmetric equivalence rule cited acp_effort_test.go
as capturing unprefixed model ids from both `hermes acp` and jcode. Only the
jcode half holds.

Verified against a live `hermes acp` v0.20.0 — the same version the fixture
names — driving `initialize` + `session/new`, the two calls this backend makes:
`models.currentModelId` came back provider-encoded in all three cases (no model
param, a bare model param, and a provider-encoded one), and all nine
availableModels ids were prefixed too. Sending a bare `gpt-5.6-terra` returned
`openai-codex:gpt-5.6-terra`, which is the defect this gate fixes and is now
first-hand rather than inferred.

Hermes Agent's encoder does have a bare branch — acp_adapter/server.py
`_encode_model_choice` returns the model alone when the provider is empty,
confirmed by calling it directly — but `_build_model_state` defaults the
provider (`state.agent.provider or detect_provider() or "openrouter"`) and
`normalize_provider("")` falls back to `openrouter`, so a normal install never
reaches it. The `hermes-4` in that fixture is better read as a simplification
made while writing a test about configOptions, not a verbatim capture.

The rule itself is unchanged and still correct: this backend serves Hermes
Agent and jcode, jcode really does report unprefixed ids, and refusing to
confirm an explicit provider against a bare current id costs nothing.

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

---------

Co-authored-by: Garfield <[email protected]>
Co-authored-by: Bohan-J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-03 13:53:56 +08:00

Multica

Multica

Agents that show up on the board.

Multica is an open-source workspace where you assign work to AI coding agents the way you'd assign it to a teammate — they pick up the issue, report progress, raise blockers, and hand it back for review. Self-hostable, works with 26 agent CLIs, no lock-in.

CI Release GitHub stars Discord

Website · Docs · Quickstart · Download · Vision · Self-Hosting · Discord · X

English | 简体中文

A Multica board where six agents and their human teammates are moving work across columns

Your next 10 hires won't be human.


What is Multica?

You already run Claude Code, Codex, and three other agents. Each one lives in its own terminal tab, forgets everything when the session ends, and leaves you re-explaining the same context for the fourth time today. The more agents you add, the more of your day goes to babysitting them.

Multica puts those agents and your teammates in one workspace. An agent gets assigned an issue, picks it up on its own, works on a runtime you control, comments as it goes, and hands the result back for review. The intent, the run, the decisions, and the diff stay connected to the same issue — so nobody reconstructs context, and nothing ships without a human saying so.


Build the team.

Claude Code, Codex, Cursor, Kimi — you don't pick one. You hire them all.

  • 26 agent CLIs → Claude Code, Codex, Cursor, Copilot, Kimi, OpenCode, and more.
  • Agents as teammates → Give each one a name, a provider, and a runtime — they show up on the board like anyone else.
  • Squads → Put agents and people on one team; the leader routes the work.
  • Skills → Turn a solved problem into a playbook every agent reuses.
  • Your own runtime → Their desk is your machine — a daemon on your laptop or cloud box. Code never leaves it.

Hand off the work.

It starts as three rough sentences in an issue. It ends as a pull request.

  • Assign an issue → Pick an agent as assignee the way you'd pick a colleague — it takes the work from there.
  • Autopilots → Run standups, audits, and reports on a cron — nobody to remind.
  • Chat → Ask your workspace a question, or start work without filing anything.
  • Projects → Group work and attach the repos and docs agents need as context.

Stay in the loop.

Which agent touched this? What did it run? What did it cost? Open the run.

  • Execution log → Replay every tool call, command, and error, timestamped.
  • Token usage → See what each run cost, per agent and per issue.
  • Review gates → Work lands in review, not in main. You decide what ships.
  • Inbox → Get pinged when an agent needs a call, not for every step.
  • Retries and timeouts → Failed runs retry on their own, or stop and tell you why.

Make it yours.

Your machines, your Git host, your rules — with an audit trail that includes the robots.


Get started

No terminal required: sign up at multica.ai, or download Multica Desktop for macOS, Windows, and Linux — it connects the computer it runs on as a runtime automatically.

The one prerequisite: the machine that will run agents needs at least one supported agent CLI installed and signed in — Claude Code, Codex, Cursor, and friends. Multica drives them; it doesn't ship them.

Self-hosting the whole thing
curl -fsSL https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.sh | bash -s -- --with-server
multica setup self-host

On Windows, set $env:MULTICA_MODE="with-server", then run the PowerShell installer: irm https://raw.githubusercontent.com/multica-ai/multica/main/scripts/install.ps1 | iex.

This pulls the official images from GHCR and requires Docker. See the Self-Hosting Guide; if the selected GHCR tag has not been published yet, fall back to make selfhost-build from a checkout.


Your first agent in five minutes

1. Sign in. multica.ai in the browser, or open Multica Desktop.

2. Connect a computer. A runtime is any machine agents can work on — your laptop, or a cloud box. Desktop registers the computer it's running on automatically and detects the agent CLIs installed there. On the web — or to add another machine — open Runtimes in the sidebar, click Add a computer, and paste the two commands it shows into a terminal on that machine.

3. Create an agent. Open Agents in the sidebar and click New agent. Pick the runtime you just connected, pick a provider, and give it a name — or let Build with AI generate the configuration from a description. That name is how it shows up on the board and in comments.

4. Assign it something. File an issue and set the agent as assignee. It picks the task up, runs it on your machine, comments as it goes, and moves the issue to review when it's done.

Full walkthrough: Quickstart · Tutorial


Runtimes

Multica does not ship a model. It drives the agent CLIs you already have installed and authenticated, so switching providers is a dropdown, not a migration.

Provider CLI Provider CLI
Claude Code claude OpenAI Codex codex
Cursor Agent cursor-agent GitHub Copilot CLI copilot
OpenCode opencode OpenClaw openclaw
Hermes hermes Pi pi
Antigravity agy CodeBuddy codebuddy
DevEco Code deveco Grok grok
Kimi kimi Kiro CLI kiro-cli
Qoder CLI qodercli Qoder CN qoderclicn
Qwen Code qwen QwenPaw qwenpaw
Reasonix reasonix Trae CLI traecli
DeepSeek Harness dsh Oh-My-Pi omp
MiniMax Code mcode Dim dim
Huawei Cloud CodeArts codearts — —

Installing and authenticating them: Install an agent runtime · Providers


Documentation

I want to… Start here
Get an agent doing something today Quickstart · Tutorial
Understand how the pieces fit Core concepts · How Multica works
Create and configure agents Agents · Create an agent · Skills
Get work to an agent Triggering agents · Assigning issues · Mentions
Connect my machines Daemon and runtimes · Install an agent runtime
Connect Git and chat tools GitHub · Self-hosted Git · Channels
Run it on my own infrastructure Self-hosting · Security model · Environment variables
Script it CLI reference · CLI and daemon guide · Auth tokens
Drive Multica from Codex, Claude Code, or Cursor Multica CLI skill
Work out why an agent is stuck Tasks · Troubleshooting

Architecture

        Web  ·  Desktop (macOS/Windows/Linux)  ·  iOS
                          │
                          ▼
   ┌──────────────┐   ┌──────────────┐   ┌──────────────────┐
   │   Next.js    │──>│  Go backend  │──>│   PostgreSQL     │
   │   frontend   │<──│  (Chi + WS)  │<──│   (17)           │
   └──────────────┘   └──────┬───────┘   └──────────────────┘
                             │  tasks over WebSocket
                      ┌──────┴───────┐
                      │ Agent daemon │  runs on your machine, next to your code
                      └──────┬───────┘
                             │  spawns
                      ┌──────┴───────────────────────────────┐
                      │  Claude Code · Codex · Cursor · …    │
                      │  (any of the 26 runtimes above)      │
                      └──────────────────────────────────────┘
Layer Stack
Web Next.js 16 (App Router)
Desktop Electron, sharing the web UI packages
Mobile Expo / React Native (iOS)
Backend Go (Chi router, sqlc, gorilla/websocket)
Database PostgreSQL 17 (pgcrypto + pg_trgm)
Agent runtime Local daemon executing any of the 26 agent CLIs above

Development

Contributors: start with the Contributing Guide.

Prerequisites: Node.js 22, pnpm 10.28.2, Go 1.26.6, Docker

make dev

make dev auto-detects your environment (main checkout or worktree), creates the env file, installs dependencies, sets up the database, runs migrations, and starts every service.

See CONTRIBUTING.md for the full workflow, worktree support, testing, and troubleshooting. The iOS client lives in apps/mobile/ — its README covers building it onto your own iPhone.

We release most weekdays, so main moves quickly — pull often.


Why "Multica"?

Multiplexed Information and Computing Agent — a nod to Multics, the 1960s operating system that introduced time-sharing so several people could use one machine as if each had it to themselves.

Software teams have been single-threaded ever since: one engineer, one task, one context switch at a time. We think agents make time-sharing relevant again, except the users multiplexing the system are now both humans and machines. A small team shouldn't feel small.

The longer argument, and where we think this goes: VISION.md.


License

Multica License — the complete Apache License 2.0 text plus additional conditions covering hosted services, commercial embedding, and branding. Self-host it, modify it, build on it; the exact terms are in the LICENSE, attribution notices in NOTICE.

S
Description
Make humans and AI agents work as one team — open-source and self-hostable. — mirror of https://github.com/klinsmaya/multica
https://multica.ai
go
Readme Apache-2.0
179 MiB
Languages
Go 55.2%
TypeScript 38.3%
MDX 5%
PLpgSQL 0.4%
JavaScript 0.4%
Other 0.6%