Use Heliast with Bifrost
This page sets up the Heliast plugin for the Bifrost gateway. Read use Heliast with a gateway you already run first.
Tested with the Bifrost HTTP gateway release transports/v1.6.11 (core 1.7.10), on chat completions, streamed and not streamed, with governance virtual keys and teams. Other request types and releases are listed under Limits.
1. Give the hook an adapter token
Create a secret value for the plugin and set it on the Heliast hook:
GATEWAY_ADAPTER_TOKENS=bifrost=<a random value of at least 32 bytes>
The same value goes to the plugin in step 3. Keep it out of any repository. To let Bifrost users have a role other than standard, also set GATEWAY_ADAPTER_ROLES=bifrost=standard|premium on the hook.
2. Get a plugin built for your Bifrost release
Bifrost loads plugins as Go shared objects. A plugin loads only if it was built with the same Go toolchain, the same dependency versions and the same flags as the Bifrost binary that loads it. So Heliast builds the plugin inside the module of each Bifrost release, and tests that a gateway built from that release loads it.
Heliast supports the Bifrost releases on its published list. Today that is transports/v1.6.11. A release that is not on the list has no commitment. Each build comes with a build record: the Bifrost release and core version, the Go toolchain, the operating system and architecture, the flags, and the hashes of the plugin, its source and the module's dependency list. Bifrost supports plugins on Linux and macOS only, and the plugin is built for the system it will run on. If you upgrade Bifrost, you need a plugin built for the new release.
Bifrost's published container image is built statically, and a static binary cannot load a plugin. We expect the stock image not to work and have not tested it. The plugin is for a Bifrost you build from source for a listed release, linked dynamically, with the plugin built beside it. Heliast supplies the plugin source, the build script and the build record. To build the plugin inside your own Bifrost source tree, with the flags you build Bifrost with:
bash scripts/build-bifrost-plugin.sh --release v1.6.11 \
--transports-dir <your Bifrost transports directory> \
--build-flags "<the flags you build Bifrost with>"
Build Bifrost with the same Go toolchain and the same flags, then load the plugin as in step 3.
3. Add the plugin to Bifrost
Add it to the plugins array in your Bifrost config.json. Custom plugins run after Bifrost's built-in plugins by default, which is what you need: the plugin asks the hook after governance has resolved the virtual key.
{
"plugins": [
{
"enabled": true,
"name": "heliast",
"path": "/path/to/heliast.so",
"config": {
"hook_url": "https://heliast-hook.internal:8095"
}
}
]
}
| Setting | What it does |
|---|---|
hook_url | The address of the Heliast hook. Required. May also be set as HELIAST_HOOK_URL. Must be https, or on the same machine; set HELIAST_ALLOW_INSECURE_HOOK=1 to allow another plain http address. |
hook_token | The adapter token from step 1. Required. Prefer the environment variable HELIAST_HOOK_TOKEN so the token is not in the file. |
timeout_seconds | How long to wait for the hook before refusing the request. Default 5. |
default_groups | The group for a virtual key that belongs to no team or customer. Leave it empty unless you mean every such key to be entitled as that group. |
default_role | The role asserted for every request. Default standard. The hook accepts it only if you allowed it for this adapter. |
on_model_mismatch | refuse (default) withholds a non-streamed answer from a model the hook did not decide on. log records it and delivers the answer. |
Restart Bifrost. The log shows plugin status: heliast - active when the plugin has loaded.
4. Map your callers
The plugin tells the hook who Bifrost authenticated. The hook decides entitlement from that.
- The subject is the virtual key's name, or its id if it has no name. The ledger shows it as
gw-bifrost:<subject>. Virtual key names are not unique in Bifrost, so give each key its own name. - The group is the virtual key's team name, then its team id, then its customer name, then
default_groups. Your Heliast policy entitles groups to models, so name the group in your policy the same as the Bifrost team, and give teams names without spaces. - The key id on the ledger is the virtual key's id, shown as
gw-bifrost:<id>. The key's value is never sent.
5. Use admitted model names
Bifrost clients send provider/model, for example openai/model-a. The plugin sends the hook the model name without the provider. Heliast decides the model id, and the provider says where it runs. A model name your gateway invents is refused unless a host record maps it to an admitted id.
6. Close the direct routes
Allow connections to your model servers only from Bifrost, and connections to the hook only from Bifrost and your operators.
7. Check it
- Send a chat request for an admitted model. It is answered. The ledger has a decision row and an inference row with token counts, both with the same request id.
- Send a request for a model that is not admitted. It is refused with the reason, nothing reaches a model, and the ledger has a denial row.
- Stop the hook and send a request. It is refused.
- Make the ledger unavailable and send a request. It is refused.
Fallbacks
Bifrost asks the plugin once for every attempt, including each fallback. A fallback to an admitted, entitled model is decided on its own and recorded as that model. A fallback to a model that is not admitted is refused, and no answer comes back. You do not need to restrict fallbacks to stay inside admission, though you may want to for other reasons.
Limits
- Request types. Chat completions, text completions, embeddings and the model listing are decided. Every other type is refused, including the responses API, images, audio and reranking.
- Routes that skip the plugin. Measured: every route that carries a model call, including the OpenAI, Anthropic and LiteLLM compatible routes, the passthrough routes, batches, videos and the MCP tool route, was either decided or refused. None reached a model without a decision.
- Streaming. A streamed answer is reported when the stream ends. It cannot be withheld after it is sent.
- Provider. The ledger records the model id. It does not record which Bifrost provider served it.
- Releases. Only
transports/v1.6.11is on the supported list. A new Bifrost release needs a new plugin build, and a test. - Stock image. The plugin is not expected to load into Bifrost's published static container image. Not tested.
- Not tested. Retries inside one provider, a client that disconnects mid-stream, Bifrost's enterprise user-level governance, tool calls through Bifrost's MCP support, and load.
- Report failures. If the hook cannot write the inference row after the answer, the plugin logs an error and the answer is still delivered. The hook counts the failure on its own metrics.