Mindctl is an LLM router that uses built-in logic and a System 1 decision model like Jev or Laya to intelligently route requests to the appropriate LLM model based on the input prompt.
💻 Visit stormsoftware.com to stay up to date with this developer
Important
This repository, and the apps, libraries, and tools contained within, is still in it's initial development phase. As a result, bugs and issues are expected with it's usage. When the main development phase completes, a proper release will be performed, the packages will be available through NPM (and other distributions), and this message will be removed. However, in the meantime, please feel free to report any issues you may come across.
- Install
- Configuration
- Command Line Interface
- Development
- Roadmap
- Contributing
- Support
- License
- Contributors ✨
The following installation methods are available for setting up Mindctl:
Install the native CLI through npm:
npm install --global mindctl
mindctl version
npx mindctl versionThe npm package includes the supported Linux, macOS, and Windows native binaries. Gateway mode still requires a runtime configuration file and the required environment variables.
Download the archive for your platform from the GitHub Release, verify it
against checksums.txt, unpack it, and run:
./mindctl version
./mindctl --config ./config.yamlOn Linux, run sha256sum --ignore-missing --check checksums.txt. On macOS,
compare shasum -a 256 <archive> to the matching checksums.txt entry.
brew tap storm-software/mindctl https://github.com/storm-software/mindctl
brew install mindctlPull a versioned image and mount a runtime configuration file:
docker pull ghcr.io/storm-software/mindctl
docker run --rm -p 8080:8080 \
-v "$PWD/config.yaml:/etc/mindctl/config.yaml:ro" \
--env-file .env \
ghcr.io/storm-software/mindctlThe image runs as a non-root user and contains no usable configuration,
credentials, encryption keys, or database. Mount a writable data volume and
configure its SQLite path in config.yaml.
The GHCR package is public after its first release: an organization owner must
set the package visibility to Public in GitHub Packages before advertising
the image. Confirm an anonymous docker pull succeeds for the release tag.
Install Mindctl using the Go toolchain:
go install github.com/storm-software/mindctl/cmd/mindctl@latestdevenv shell -- go run ./cmd/mindctl --config ./config.yamlMindctl reads one strict YAML document. Unknown fields and multiple YAML
documents are rejected. Start from
config.example.yaml, set the environment variables it
names, and choose a writable SQLite database path.
When no --config argument is supplied, Mindctl resolves the configuration in
this order:
${XDG_CONFIG_HOME}/mindctl/config.yaml, when that file exists. WhenXDG_CONFIG_HOMEis unset, Mindctl uses~/.config/mindctl/config.yaml.config.example.yaml, for source-checkout usage when no user config exists.
An explicit --config <path> always takes precedence. The legacy
-config <path> spelling is also accepted. Installed binaries do not include
config.example.yaml, so create the user configuration before running one
without --config:
mindctl_config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/mindctl"
mkdir -p "$mindctl_config_dir"
cp config.example.yaml "$mindctl_config_dir/config.yaml"
chmod 700 "$mindctl_config_dir"
chmod 600 "$mindctl_config_dir/config.yaml"The example configuration references environment variables rather than storing
secret values. Export values for its gateway token, LAYA_CLASSIFIER_TOKEN, and encryption
key before startup. Keep the configuration and SQLite database in locations
the Mindctl process can read and write, respectively.
Container deployments should continue to mount the configuration explicitly at
/etc/mindctl/config.yaml, as shown above; the image command supplies that
path through its configuration flag.
Compression is opt-in:
headroom:
enabled: true
mode: cache # cache or tokenOn first use, an enabled installation may need network access and writable
private cache storage to provision the pinned Headroom runtime. Mindctl sends
only round-trip-safe assistant and string tool-result text to a loopback,
token-authenticated child; provider credentials and inference requests never
go to that child. Compression failures are fail-closed and return HTTP 503
with code headroom_unavailable; Mindctl never forwards the uncompressed
request. Disable it by setting headroom.enabled: false and restarting.
Do not enable this option when another Headroom proxy already fronts Mindctl, or the request may be compressed twice.
Set top-level debug: true, export MINDCTL_DEBUG=true, or start the gateway
with --debug to write detailed JSONL traces to
${XDG_CACHE_HOME:-$HOME/.cache}/mindctl/logs. An explicit
--debug=true|false overrides MINDCTL_DEBUG, which overrides the YAML value.
Each gateway process creates a private trace file containing request IDs,
derived feature counts, classifier signals, policy candidates and rejections,
selected routes, provider attempts, and stream retries. Trace files exclude
prompt and response content, credentials, request headers, and raw provider
errors.
Mindctl can keep the routable model allow-list separate from the gateway
catalog. The gateway reads
${XDG_STATE_HOME:-$HOME/.local/state}/mindctl/providers.yaml once during
startup and caches the resulting availability in memory. When the file is
absent, each model's available value in config.yaml remains authoritative.
When the file exists, only the models listed in it are available:
providers:
openai:
- gpt-6-astra
- gpt-6-sol
- gpt-6-luna
- gpt-5.6-sol
- gpt-5.6-terra
- gpt-5.6-luna
- gpt-5.3-codex
- gpt-5.3-codex-spark
deepseek:
- deepseek-flashUse the catalog commands to inspect or update the file. The first update
initializes it from the current available values, and changes take effect in
the gateway after restart.
mindctl model list
mindctl model list --all
mindctl model enable openai.gpt-6-astra
mindctl model disable openai
mindctl provider list
mindctl provider list --all
mindctl provider enable openai
mindctl provider disable openaiThe Laya classifier is optional. To launch the local sidecar, set a shared bearer token and start its opt-in Compose profile:
export LAYA_CLASSIFIER_TOKEN="$(openssl rand -hex 32)"
docker compose -f compose.laya.yaml --profile laya up --build -dcompose.laya.yaml keeps port 8091 inside the named mindctl Docker network;
it does not publish a host port. Run the gateway in that network and configure
the endpoint shown in config.example.yaml:
classifier:
endpoint: http://laya:8091
token_env: LAYA_CLASSIFIER_TOKENThe sidecar requires Authorization: Bearer <LAYA_CLASSIFIER_TOKEN> on
POST /v1/classify. It pins convaiinnovations/laya and the
typed-decisions variant, downloads model files only on its first startup,
and retains them in the laya-model-cache volume. Normal Go and Python tests
do not download model weights.
For an operator-managed sidecar, point the same classifier: configuration at
an absolute HTTPS endpoint instead. It must implement the documented
mindctl.classifier.v1 bearer-authenticated /v1/classify contract; Mindctl
continues to make deterministic routing decisions and falls back safely when
the endpoint is unavailable.
The Mindctl command line interface provides several commands to manage the application, its configuration, models, and providers. A complete list of available commands can be found in the CLI documentation.
Check whether Codex or Claude Code is configured to use the Mindctl router:
mindctl status
mindctl status codex
mindctl status claudeThe first command lists harnesses and their configuration status; the other
commands print one status. Codex checks the top-level model_provider in
~/.codex/config.toml; Claude checks the global env.ANTHROPIC_BASE_URL in
~/.claude/settings.json against http://127.0.0.1:8080 (with one optional
trailing slash). Neither command checks project-level or shell overrides or
tests whether the gateway is running or reachable.
Enter the repository's development environment before running Go commands:
devenv shell -- go test ./...More information can be found in the Mindctl documentation.
Run devenv shell -- goreleaser release --snapshot --clean to build local
release archives in dist/ without publishing them.
Run devenv shell -- go run ./cmd/mindctl to start the gateway with the home
configuration when present, or pass --config ./config.yaml to use a project-
local file explicitly.
Mindctl can route Codex requests through the ChatGPT account already signed in
to Codex. Configure Mindctl with chatgpt_oauth_passthrough as shown in
config.example.yaml or your active configuration file,
then add this custom provider to
the Codex config.toml:
model = "mindctl-auto"
model_provider = "mindctl"
[model_providers.mindctl]
name = "Mindctl"
base_url = "http://127.0.0.1:8080/v1"
wire_api = "responses"
requires_openai_auth = true
env_http_headers = { "X-Mindctl-Token" = "MINDCTL_GATEWAY_TOKEN" }mindctl-auto enables Mindctl's automatic routing. A concrete model name is an
explicit selection and must match a model ID in Mindctl's configured catalog.
Set explicit_only: true on a catalog model reserved for explicit requests,
such as a dedicated approval reviewer. Mindctl keeps it out of automatic
routing, classifier choices, and fallback candidates; an explicit request
still requires normal model availability, capabilities, tier bounds, and
provider credentials. The default is false for existing models. An
explicit-only model's failed stream does not escalate to a different model.
With chatgpt_oauth_passthrough, Mindctl adds codex-auto-review to the
effective OpenAI catalog as an available, explicit-only model; no YAML model
entry is needed. API-key providers do not get this entry. A manually configured
codex-auto-review entry is retained only when it belongs to openai and has
explicit_only: true; otherwise configuration loading fails. Its catalog
capacity and capabilities are local routing settings, not a guarantee of
account access or upstream limits. If providers.yaml already exists and
omits it, enable the reviewer with
mindctl model enable openai.codex-auto-review and restart the gateway.
Sign in to Codex with ChatGPT and export MINDCTL_GATEWAY_TOKEN with the same
gateway token supplied to Mindctl. Do not set OPENAI_API_KEY for this
provider. Codex owns OAuth login and token refresh; Mindctl forwards the
request-scoped credential only to the configured ChatGPT Codex endpoint.
Available models, workspace access, rate limits, and usage limits remain
subject to the selected ChatGPT account and subscription. The shipped
ChatGPT Pro catalog includes gpt-6-astra, gpt-6-sol, gpt-6-luna,
gpt-5.6-sol, gpt-5.6-terra, gpt-5.6-luna, gpt-5.3-codex, and the
Pro-only research preview gpt-5.3-codex-spark. GPT-6 model access is
currently rolling out and may not yet appear for every Pro account.
Mindctl can also route requests through a caller-managed Claude subscription
credential. Generate a long-lived token with claude setup-token, export it as
CLAUDE_CODE_OAUTH_TOKEN, and configure the Anthropic provider with
claude_oauth_passthrough as shown in
config.example.yaml.
Add the Claude credential to the Codex provider's environment-backed headers:
[model_providers.mindctl]
name = "Mindctl"
base_url = "http://127.0.0.1:8080/v1"
wire_api = "responses"
requires_openai_auth = true
env_http_headers = { "X-Mindctl-Token" = "MINDCTL_GATEWAY_TOKEN", "X-Mindctl-Claude-Token" = "CLAUDE_CODE_OAUTH_TOKEN" }Codex sends the Claude token to Mindctl on each request. Mindctl keeps it in request context only, makes subscription-backed Anthropic models eligible for that request, and forwards it to the configured Anthropic endpoint as a bearer credential. Mindctl does not store, refresh, or log the token.
The shipped catalog includes Claude Fable 5.1, Opus 5.5, Sonnet 5, and Haiku 4.5 with comparable Claude API prices for routing decisions. Actual usage is governed by the selected Claude subscription's model access and plan limits, not API token billing.
To route Claude Code itself, set client_auth.header: X-Mindctl-Token and
claude_messages.enabled: true in the gateway configuration. Configure the
Anthropic provider with claude_oauth_passthrough and the models you want in
the Mindctl catalog. The endpoint is POST /v1/messages; it is absent by
default, and /v1/responses keeps its existing authentication behavior.
Set only the non-secret gateway origin in your global Claude settings:
{"env":{"ANTHROPIC_BASE_URL":"http://127.0.0.1:8080"}}Let Claude Code retain its native subscription login. Do not set
ANTHROPIC_AUTH_TOKEN, ANTHROPIC_API_KEY, or an apiKeyHelper for this
connection. Supply the separate gateway token from your existing secret store
in a trusted launcher instead of checking it into Claude settings:
#!/bin/sh
: "${MINDCTL_GATEWAY_TOKEN:?load the gateway token from your secret store}"
export ANTHROPIC_CUSTOM_HEADERS="X-Mindctl-Token: ${MINDCTL_GATEWAY_TOKEN}"
exec claude "$@"mindctl-auto can choose any eligible configured provider. A subscription
credential is sent only to the Anthropic OAuth provider; other providers
require their own API credentials and may incur separate billing. A specific
model must match a Mindctl catalog ID; configure Claude Code's main and
background model overrides deliberately, as built-in aliases are not
automatically remapped. Signed thinking restricts a turn to Anthropic.
For an existing Home Manager setup managed by Headroom, choose Mindctl
explicitly in that separate configuration before enabling this setting; to
roll back, restore Headroom's ANTHROPIC_BASE_URL and remove the launcher
header. Mindctl does not change the managed settings automatically. Confirm
the actual login and a streamed tool round-trip in a live Claude Code session
before relying on the connection.
See the open issues for a list of proposed features (and known issues).
- Top Feature Requests (Add your votes using the 👍 reaction)
- Top Bugs (Add your votes using the 👍 reaction)
- Newest Bugs
First off, thanks for taking the time to contribute! Contributions are what makes the open-source community such an amazing place to learn, inspire, and create. Any contributions you make will benefit everybody else and are greatly appreciated.
Please try to create bug reports that are:
- Reproducible. Include steps to reproduce the problem.
- Specific. Include as much detail as possible: which version, what environment, etc.
- Unique. Do not duplicate existing opened issues.
- Scoped to a Single Bug. One bug per report.
Please adhere to this project's code of conduct.
You can use markdownlint-cli to check for common markdown style inconsistency.
Reach out to the maintainer at one of the following places:
This project is licensed under the Apache License 2.0. Feel free to edit and distribute this template as you like. If you have any specific questions, please reach out to the Storm Software development team.
See LICENSE for more information.
Thanks goes to these wonderful people (emoji key):
Patrick Sullivan 🎨 💻 🔧 📖 |
Tyler Benning 🎨 |
Stormie 🚧 |
||||
|
|
||||||
This project follows the all-contributors specification. Contributions of any kind welcome!
Storm Software is an open source software development organization and creator of Acidic, StormStack and StormCloud.
Our mission is to make software development more accessible. Our ideal future is one where anyone can create software without years of prior development experience serving as a barrier to entry. We hope to achieve this via LLMs, Generative AI, and intuitive, high-level data modeling/programming languages.
Join us on Discord to chat with the team, receive release notifications, ask questions, and get involved.
If this sounds interesting, and you would like to help us in creating the next generation of development tools, please reach out on our website or join our Slack channel!

