Muse Spark in Hermes Agent billed to the Muse Code subscription
Provider plugin for Hermes Agent that signs in with a Muse Code subscription. A successful login does not guarantee model access: Meta may reject subscription requests from clients other than Muse Code.
Unofficial and experimental. This plugin uses a Muse Code subscription credential from Hermes Agent. Meta's terms limit that credential to Muse Code, so this use may not be supported and Meta may suspend or revoke accounts or credentials. Use it at your own risk; the authors cannot recover an account. Read DISCLAIMER.md before installing.
hermes plugins install TheStreamCode/hermes-muse-code --enableHermes loads the provider from $HERMES_HOME/plugins/muse-code-subscription. Restart long-lived
Hermes processes after installing or after re-running the login script.
-
Start the Meta device login:
hermes auth add muse-code -
Approve the code in the browser. If the browser does not open, use the printed URL and code.
-
Start a chat with the subscription provider, or pick it from the model picker. Sign in before opening the picker: Hermes otherwise treats this provider as an API-key provider.
hermes chat -m muse-spark-1.3 --provider muse-code hermes model
The standalone login script remains available for existing installations. The login mints an
account-bound inference key and caches it locally. The cache stores
exactly apiKey, accountId, and email; the OAuth access token from the login flow is kept in
memory only and never written to disk. The key is never printed. A 401 may indicate a revoked key;
the mint endpoint is rate-limited, so the plugin never re-mints on its own.
- Registers a
muse-codeprovider over the Responses API, the same wire as the bundled Meta provider, so prompt caching applies. Inference goes through a local loopback wire adapter that forwards tohttps://api.meta.ai/v1. - Runs the wire adapter (
muse_adapter.py,127.0.0.1only, first free port of 43219-43222): it aliases only the client tool names that collide with Muse Code's builtin tool names (the full pinned set is in ADAPTER_CONTRACT.md), restores them in streamed and JSON responses, and normalizes the Meta Responses parameters (tool_choice, reasoning effort, output-token floor, encrypted-reasoning include). If Meta still refuses the request, the adapter retries once with every tool aliased.MUSE_CODE_ADAPTER=offkeeps the proxy but disables the reshaping. - Owns the Meta device-code login (RFC 8628); authentication is an explicit, user-driven step, and no third-party CLI is required or invoked.
- Discovers models live from your account catalog: every returned model id is served except the
endpoint families verified as non-chat (
sam-*,muse-image-*,muse-voice-*), so newly published models appear with no plugin update. - Exports the cached key as
MUSE_CODE_SUB_TOKENin the process environment and refreshes it when the cache changes; an explicit value always wins. - Keeps the credential cache owner-only and never persists the OAuth access token.
- Runs stdlib-only: no runtime dependencies beyond the Hermes host.
Meta's Responses endpoint accepts the Muse Code subscription credential only for requests whose
declared flat tool names differ from Muse Code's builtin names. The plugin therefore starts a
loopback reverse proxy (muse_adapter.py) on 127.0.0.1 when it loads. It renames exactly the
colliding tool declarations to client_<name> (with _2, _3, ... when that alias is already
taken), restores the original names in streamed and JSON responses, repairs empty function-call
arguments from the streamed deltas, and normalizes four Responses parameters Meta requires
(tool_choice to auto, reasoning_effort to reasoning.effort, an output-token floor of 16, and
reasoning.encrypted_content in include). Other paths, fields and headers are relayed unchanged.
This is a compatibility layer for the Meta Responses wire format, not an authentication change: the
Authorization header is forwarded untouched and no credential, token or signature is created.
ADAPTER_CONTRACT.md pins the rules; MUSE_CODE_ADAPTER=off keeps the loopback proxy but disables
the reshaping. The adapter shuts down with the process.
- Hermes Agent
>= 0.22.0with model-provider plugin discovery. - Python 3.11 or newer for the one-time login script; the script uses the standard library only.
- An active Muse Code subscription on the Meta account used at login.
- CI runs the unit suite on Python 3.11 and the local plugin validator.
| Variable | Purpose | Default |
|---|---|---|
MUSE_CODE_SUB_TOKEN |
Explicit subscription key. Wins over the cache; set it to pin a key manually. | from the login cache |
MUSE_CODE_SUB_CREDENTIALS |
Overrides the path of the credential cache. | $HERMES_HOME/muse-code-sub.json |
MUSE_CODE_ADAPTER |
off keeps the local proxy but disables the tool-name reshaping and parameter normalization. |
unset |
Select the provider as muse-code in hermes model, or pass --provider muse-code to
hermes chat. The provider base URL is the loopback adapter's /v1 origin
(http://127.0.0.1:<port>/v1), which forwards to https://api.meta.ai/v1; there is no
user-facing endpoint override (META_BASE_URL is ignored). If no loopback port can be bound, the
provider falls back to the direct https://api.meta.ai/v1 endpoint and logs a warning.
The subscription key lives owner-only in the local credential cache and in process memory; it is
never logged and never written to a configuration file. The plugin talks only to auth.meta.com
for the device login and to api.meta.ai for key minting and inference. Inference requests pass
through the local wire adapter, which listens on 127.0.0.1 only, forwards the Authorization
header unchanged, and never reads, stores or logs credentials, request bodies or response bodies;
its optional debug line carries only the method, the path, the upstream status and the count of
adapted tool names. The cache stores apiKey, accountId, and email; nothing else is read back.
To remove access, delete $HERMES_HOME/muse-code-sub.json (or the path in
MUSE_CODE_SUB_CREDENTIALS) and restart Hermes. Run muse logout if you also want to clear the
Muse Code CLI session. See SECURITY.md for vulnerability reporting,
AUTH_CONTRACT.md for the pinned login contract, and
ADAPTER_CONTRACT.md for the inference wire rules.
No completed login was found. Run hermes auth add muse-code; a missing or empty cache means no
login has completed yet. Confirm that the login used the same HERMES_HOME as the Hermes process.
The key may have been revoked or rotated. Run hermes auth add muse-code once. Do not repeat the
login in a loop: the mint endpoint is rate-limited.
A 403 mentioning client_attestation_denied or that Muse Code subscriptions cover the Muse Code
client only means Meta applied its client restriction to the request. The plugin's loopback wire
adapter exists for exactly this case: it aliases the client tool names that collide with Muse
Code's builtin names, so the refusal should no longer appear for them. If it still does, one of
these is true:
- the adapter could not bind a loopback port, the plugin log names the failure, and the provider fell back to the direct endpoint;
MUSE_CODE_ADAPTER=offis set, or an older plugin version is still loaded;- Meta changed the gate, for example to a real per-client signature. Nothing in this plugin can forge a signature, so in that case the subscription cannot be used from Hermes.
The refusal is a client restriction, not evidence of an expired key: signing in again does not remove it. Use Muse Code for the subscription, or a separate pay-as-you-go Meta Model API key for other clients, subject to Meta's terms.
The cache is read once per process. hermes chat always sees a fresh login, but long-lived
gateway workers must be restarted after re-running the login script. The same applies after
hermes plugins update muse-code-subscription: the running gateway keeps the previous plugin
version until it is restarted with hermes gateway restart --yes.
An explicit MUSE_CODE_SUB_TOKEN wins over the key minted by the subscription login, by design. If
it is set in the process environment or in $HERMES_HOME/.env to an old Meta API key (for example
a pay-as-you-go key), Meta answers 402 billing_not_configured for that key and the subscription
key is never used. Remove the line from .env (or from the shell profile) so the plugin exports
the minted key, remove any pooled credential created from it with hermes auth remove muse-code <id>, then restart the gateway with hermes gateway restart --yes. hermes auth status muse-code
shows the resolved state.
A base_url under model: in $HERMES_HOME/config.yaml overrides the provider profile, so the
loopback adapter is bypassed and the client restriction returns. The hermes model provider picker
writes that field. Remove the model.base_url line and restart the gateway; the plugin's profile
then supplies the adapter URL. Keep model.provider: muse-code and the default model.
Requirements: Python 3.11 or newer and the Hermes checkout used by CI.
git clone https://github.com/TheStreamCode/hermes-muse-code.git
cd hermes-muse-code
python -m pytest -q
python .github/scripts/validate-plugin.py --path .
ruff check .The unit suite is hermetic: Hermes imports are stubbed, network calls are mocked, and no Meta
account is needed. CI runs the unit suite, the local plugin validator, and ruff. The plugin
stays stdlib-only by design; do not add third-party runtime dependencies.
Usage questions and workflows belong in GitHub Discussions; reproducible defects belong in GitHub Issues using the bug report form. See SUPPORT.md. Problems that also reproduce without this provider belong to the Hermes Agent project. The plugin cannot obtain, restore, or protect a Meta account, subscription, or credential.
See CONTRIBUTING.md. Pull requests must pass the required CI checks.
MIT β see LICENSE. The software is provided "as is"; see
DISCLAIMER.md for the risk notice and liability terms. Flow parameters are
compatible with the published behavior of oh-my-pi (MIT); see NOTICE.
Maintained by Michael Gasperini (Mikesoft). If this project is useful to you, consider sponsoring its maintenance.