{"id":50967660,"url":"https://github.com/dymoo/fusion","last_synced_at":"2026-06-18T22:02:24.886Z","repository":{"id":365686979,"uuid":"1273096871","full_name":"dymoo/fusion","owner":"dymoo","description":"Local fusion model provider: serves OpenAI- and Anthropic-compatible endpoints and routes to a single model or a panel of models aggregated by a judge. Works with Claude Code, opencode, and any OpenAI client.","archived":false,"fork":false,"pushed_at":"2026-06-18T12:12:02.000Z","size":167,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-18T13:13:18.490Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dymoo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-18T07:52:20.000Z","updated_at":"2026-06-18T12:12:06.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/dymoo/fusion","commit_stats":null,"previous_names":["dymoo/fusion"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/dymoo/fusion","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dymoo%2Ffusion","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dymoo%2Ffusion/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dymoo%2Ffusion/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dymoo%2Ffusion/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dymoo","download_url":"https://codeload.github.com/dymoo/fusion/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dymoo%2Ffusion/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34508867,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-18T02:00:06.871Z","response_time":128,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-18T22:02:23.811Z","updated_at":"2026-06-18T22:02:24.879Z","avatar_url":"https://github.com/dymoo.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @dymoo/fusion\n\n**A local fusion model provider.** It serves **OpenAI-compatible** and **Anthropic-compatible** HTTP endpoints, then routes each request to either a **single** upstream model or a **panel** of models whose answers an orchestrator/judge model fuses into one. Point Claude Code, opencode, or any OpenAI/Anthropic client at it.\n\n- One endpoint in front of your ChatGPT/Codex subscription, local/cloud Ollama, and any OpenAI-compatible API.\n- **Smart complexity routing** (default): a local, in-process code-embedding model classifies each request and routes trivial edits to one fast model, normal work round-robin across a couple, and real planning to the whole panel. See [docs/ROUTING.md](docs/ROUTING.md).\n- **Round-robin** load spreading with **graceful failover** on rate limits and errors.\n- **Capability negotiation**: only the least-capable intersection is exposed, `max_tokens` is clamped to the smallest model's limit, and images route only to vision-capable models.\n- **Token-caching discipline** (Anthropic `cache_control` breakpoints, OpenAI/Codex prompt-cache keys) to control cost.\n- Cross-platform **background daemon** (macOS, Windows, Linux). No native dependencies.\n\n\u003e Fusion is **not an agent.** It never runs shell commands, reads files, or executes tools. It returns model text and tool-call _suggestions_ only — your coding agent stays in control of tool execution.\n\n---\n\n## Table of contents\n\n- [Quick start](#quick-start)\n- [How routing works](#how-routing-works)\n- [Configure upstreams](#configure-upstreams)\n- [Run as a daemon](#run-as-a-daemon)\n- [Point a coding agent at it](#point-a-coding-agent-at-it)\n- [OpenAI Codex / ChatGPT auth](#openai-codex--chatgpt-auth)\n- [Example requests](#example-requests)\n- [Capabilities \u0026 limits](#capabilities--limits)\n- [Token caching](#token-caching)\n- [Configuration reference](#configuration-reference)\n- [Limitations](#limitations)\n- [Run the tests](#run-the-tests)\n- [For AI agents](#for-ai-agents)\n- [License](#license)\n\n---\n\n## Quick start\n\nRequires **Node.js ≥ 22**.\n\n```bash\n# 1. Install (global, or use npx)\nnpm install -g @dymoo/fusion        # or: pnpm add -g @dymoo/fusion\n\n# 2. Create a config\ncurl -fsSL https://raw.githubusercontent.com/dymoo/fusion/main/config.example.yaml -o config.yaml\n#   …or, from a clone:  cp config.example.yaml config.yaml\n$EDITOR config.yaml\n\n# 3. (If using your ChatGPT/Codex subscription) sign in once\nfusion auth login                    # or reuse an existing `codex` CLI login\n\n# 4. Start the background daemon\nfusion start                         # reads ./config.yaml, listens on :8787\n\n# 5. Verify\ncurl -s http://localhost:8787/health | jq\ncurl -s http://localhost:8787/v1/models | jq\n```\n\nRun it in the foreground instead (useful for debugging): `fusion run`.\n\n---\n\n## How routing works\n\n`routing.mode` chooses the strategy — full details in **[docs/ROUTING.md](docs/ROUTING.md)**:\n\n| mode                  | behaviour                                                                                      |\n| --------------------- | ---------------------------------------------------------------------------------------------- |\n| `single`              | Always one model — round-robin over `pools.orchestrator` with graceful failover.               |\n| **`smart`** (default) | A local code-embedding model classifies each request into a complexity **tier** and routes it. |\n| `all`                 | Always fuse — fan out to the whole panel and aggregate with a judge (OpenRouter-Fusion style). |\n\n**Smart tiers:** `compact` (trivial → one fast model) · `regular` (normal work → round-robin 1–2 models) · `plan` (architecture/planning → the full panel + judge). The classifier embeds a few salient segments of the request (latest turn, recent turns, tool output, pasted code, system prompt) with an in-process ONNX **code** model, and also **semantically detects harness \"plan mode\" / \"/compact\"** so an actual plan-mode session fans out. It uses the GPU if available, and **never silently falls back** — if the model can't load, smart requests return 503 and `/health` shows the error. (Requests carrying `tools` are always served by a single model — the `plan`/panel tier applies only to tool-free requests; see [Limitations](#limitations).)\n\n```\n                       ┌─ single ─▶ orchestrator ring ─▶ [model A] ──(429)──▶ [model B] ✓\nclient ─▶ router ──────┤─ smart ──▶ classify(tier) ─▶ compact|regular → single   ·   plan → panel\n                       └─ all ────▶ panel ring ─▶ [A] [B] [C] ──▶ judge (orchestrator ring) ─▶ answer\n```\n\n**Override the route per request:**\n\n- OpenAI body: `\"extra_body\": {\"fusion_route\": \"single|smart|all|compact|regular|plan\", \"panel\": \"default\"}` (or `\"fusion_tier\": \"plan\"`).\n- Header (any surface): `x-fusion-route: plan` (etc.), `x-fusion-tier: …`, `x-fusion-panel: …`.\n\nEvery response carries a debug header showing what happened:\n\n```\nx-fusion-route: mode=single; tier=regular; served_by=kimi; scores=compact:0.19,regular:0.41,plan:0.27; reason=smart[embedding]:_latest+system_→_regular=0.41\n```\n\nA recursion guard (`x-fusion-depth`) prevents a Fusion instance pointed at another Fusion from re-triggering a panel.\n\n---\n\n## Configure upstreams\n\nUpstreams live in `config.yaml`. Each has a `type`:\n\n| type                | wire protocol                      | auth                                    | capability discovery               |\n| ------------------- | ---------------------------------- | --------------------------------------- | ---------------------------------- |\n| `codex`             | OpenAI Responses (ChatGPT backend) | reuses `~/.codex` / `fusion auth login` | `~/.codex/models_cache.json`       |\n| `openai`            | OpenAI Chat Completions            | `apiKey` / `apiKeyEnv`                  | `GET /v1/models`                   |\n| `openai-compatible` | OpenAI Chat Completions            | `apiKey` / `apiKeyEnv`                  | `GET /v1/models` + models.dev      |\n| `anthropic`         | Anthropic Messages                 | `apiKey` / `apiKeyEnv`                  | `GET /v1/models` + models.dev      |\n| `ollama`            | OpenAI Chat Completions at `/v1`   | none (local)                            | `GET /api/tags` + `POST /api/show` |\n\n```yaml\nupstreams:\n  - id: codex\n    type: codex\n    models: [\"gpt-5.5\"] # current OpenAI SOTA; omit to expose all codex models\n\n  - id: ollama\n    type: ollama\n    baseURL: http://localhost:11434/v1\n    # models omitted → auto-discovered\n\n  - id: openrouter\n    type: openai-compatible\n    baseURL: https://openrouter.ai/api/v1\n    apiKeyEnv: OPENROUTER_API_KEY\n    models: [\"anthropic/claude-sonnet-4.5\"]\n\npools:\n  orchestrator: [codex, openrouter] # single-route + judge ring\n  panel:\n    default: [codex, openrouter, ollama] # opinion-givers for panel mode\n```\n\nPut secrets in environment variables (`apiKeyEnv`), not in the file. `config.yaml` is git-ignored by default.\n\n---\n\n## Run as a daemon\n\n```bash\nfusion start [--port N] [--config FILE] [--host H]   # spawn detached, log to ~/.fusion/fusion.log\nfusion status                                        # pid + /health probe\nfusion logs [--lines N] [--follow]                   # tail the log\nfusion restart\nfusion stop\nfusion run [--port N] [--config FILE]                # foreground (Ctrl-C to stop)\n```\n\nState lives in `~/.fusion/` (override with `FUSION_HOME`): `fusion.pid`, `fusion.port`, `fusion.log`, `fusion.err.log`. The daemon is a plain detached Node process — it works identically on macOS, Linux, and Windows with no native daemonizer. `fusion stop` uses `SIGTERM` then `taskkill /T /F` (Windows) / `SIGKILL` (POSIX) as a fallback.\n\n### Run it under your OS service manager (optional)\n\nAll three just wrap `fusion run`:\n\n- **Linux (systemd user unit)** — `~/.config/systemd/user/fusion.service`:\n  ```ini\n  [Service]\n  ExecStart=%h/.local/share/pnpm/fusion run --config %h/.config/fusion/config.yaml\n  Restart=on-failure\n  [Install]\n  WantedBy=default.target\n  ```\n  `systemctl --user enable --now fusion`\n- **macOS (launchd)** — a `~/Library/LaunchAgents/app.dymoo.fusion.plist` whose `ProgramArguments` are `[node, /path/to/fusion, run]`.\n- **Windows** — wrap `fusion run` with [NSSM](https://nssm.cc/) or Task Scheduler (\"At log on\").\n\n---\n\n## Point a coding agent at it\n\n### Claude Code\n\nClaude Code speaks the Anthropic Messages format, so point it at Fusion's Anthropic surface:\n\n```bash\nexport ANTHROPIC_BASE_URL=http://localhost:8787\nexport ANTHROPIC_AUTH_TOKEN=test            # only needed if you set server.authKey\nexport ANTHROPIC_MODEL=fusion               # route everything through the fusion router\nclaude\n```\n\n\u003e The model is exposed as just **`fusion`**. (Claude Code's `/model` picker auto-discovery only lists ids prefixed `claude`/`anthropic`, so set `ANTHROPIC_MODEL=fusion` via env — that works regardless of the picker.)\n\u003e\n\u003e **Agentic multi-turn:** Claude Code sends `tools` every turn. Such requests are always served by **one** model — the **actor** pool (`pools.actor`, your strongest reasoner) — and with `caching.sessionAffinity.enabled: true` the whole session **pins to it** for coherent reasoning, tool-call linkage, and prompt-cache reuse across however many turns the session runs. On **hard** turns a **council** of advisor models deliberates first and the actor acts on their briefing (`routing.council`) — multi-model reasoning without stalling the loop. See [docs/ROUTING.md](docs/ROUTING.md); defaults live in [`config.example.yaml`](config.example.yaml).\n\n### opencode\n\nAdd an OpenAI-compatible provider to `opencode.json` (project) or `~/.config/opencode/opencode.json`:\n\n```json\n{\n  \"$schema\": \"https://opencode.ai/config.json\",\n  \"provider\": {\n    \"fusion\": {\n      \"npm\": \"@ai-sdk/openai-compatible\",\n      \"name\": \"Fusion\",\n      \"options\": { \"baseURL\": \"http://localhost:8787/v1\", \"apiKey\": \"test\" },\n      \"models\": { \"fusion\": { \"name\": \"Fusion\" } }\n    }\n  },\n  \"model\": \"fusion/fusion\"\n}\n```\n\n### Any OpenAI client\n\n```\nBase URL: http://localhost:8787/v1\nAPI key:  test            (any value, unless server.authKey is set)\nModel:    fusion\n```\n\n---\n\n## OpenAI Codex / ChatGPT auth\n\nFusion can use your ChatGPT/Codex subscription with **no API key**:\n\n- **Reuse the `codex` CLI login** — if you already ran `codex login`, Fusion reads `~/.codex/auth.json` (override the dir with `CODEX_HOME`). It refreshes and rotates the token automatically and writes it back in codex's own format, so both tools keep working.\n- **`fusion auth login`** — runs the same OAuth (PKCE) loopback flow as codex on machines without the codex CLI, and saves credentials to `~/.codex/auth.json`.\n\nIf a refresh is rejected (`invalid_grant`), re-run `fusion auth login`. Tokens are never logged.\n\n---\n\n## Example requests\n\n```bash\n# Health \u0026 model list\ncurl -s http://localhost:8787/health | jq\ncurl -s http://localhost:8787/v1/models | jq\n\n# Single route (simple prompt)\ncurl -s http://localhost:8787/v1/chat/completions \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"model\":\"fusion\",\"messages\":[{\"role\":\"user\",\"content\":\"Say hello in one sentence.\"}],\"temperature\":0.2}' | jq\n\n# Panel route (hard prompt escalates) — inspect the x-fusion-route header\ncurl -si http://localhost:8787/v1/chat/completions \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"model\":\"fusion\",\"messages\":[{\"role\":\"user\",\"content\":\"I have a FastAPI service with intermittent deadlocks under load (async SQLAlchemy + Postgres). Give me a debugging plan, likely root causes, and code-level fixes.\"}]}' \\\n  | grep -i x-fusion-route\n\n# Streaming\ncurl -N http://localhost:8787/v1/chat/completions \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"model\":\"fusion\",\"stream\":true,\"messages\":[{\"role\":\"user\",\"content\":\"Stream a haiku.\"}]}'\n\n# Anthropic surface (what Claude Code uses)\ncurl -s http://localhost:8787/v1/messages \\\n  -H 'Content-Type: application/json' -H 'anthropic-version: 2023-06-01' \\\n  -d '{\"model\":\"fusion\",\"max_tokens\":256,\"messages\":[{\"role\":\"user\",\"content\":\"Hello\"}]}' | jq\n\n# Force a panel via header\ncurl -si http://localhost:8787/v1/chat/completions \\\n  -H 'Content-Type: application/json' -H 'x-fusion-route: panel' \\\n  -d '{\"model\":\"fusion\",\"messages\":[{\"role\":\"user\",\"content\":\"Design a rate limiter.\"}]}' | grep -i x-fusion-route\n```\n\n---\n\n## Capabilities \u0026 limits\n\nFusion queries each upstream's capabilities (vision, tools, context window, max output) at startup and on an interval, from provider-native sources (Ollama `/api/show`, codex models cache, `/v1/models`), a bundled models.dev registry, and your config overrides.\n\nIt then exposes the **least-capable intersection** across a route so a client never asks for something the weakest member cannot do:\n\n- **tools** are advertised only if _every_ model in the route supports them;\n- **modalities** are the set intersection;\n- **context window** and **max output** are the minimum across the route.\n\n`max_tokens` is clamped to the smallest model's output limit. If a request contains an **image**, Fusion routes only to **vision-capable** models; if none are available it returns a clear error (or strips images, per `routing.imageFallback`).\n\n---\n\n## Token caching\n\nTo control cost and usage allowances:\n\n- **Anthropic upstreams** get `cache_control` breakpoints injected on the stable prefix (tools + last system block, 1-hour TTL) and the last messages (5-minute TTL), capped at 4 breakpoints — so repeated turns read the cache at a fraction of the cost.\n- **OpenAI / Codex upstreams** get a stable `prompt_cache_key` per session (and Codex requests set `store: false`).\n- **Session affinity** (`caching.sessionAffinity.enabled`, recommended on for agentic use) pins a session to one upstream — coherent multi-turn reasoning/tool-call linkage and maximal cache reads; it coexists with round-robin (it only changes where failover starts, never removes a ring member) and re-pins on failover. The pin map is bounded so a long-running daemon can't leak.\n\n---\n\n## Configuration reference\n\nSee [`config.example.yaml`](./config.example.yaml) for a fully commented example. Top-level keys:\n\n| key            | purpose                                                                                                                                                                                                |\n| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `server`       | `host`, `port`, optional `authKey`                                                                                                                                                                     |\n| `upstreams[]`  | `id`, `type`, `baseURL?`, `apiKey?`/`apiKeyEnv?`, `models?`, `reasoningEffort?` (codex→`reasoning.effort` e.g. `xhigh`; OpenAI-compat→`reasoning_effort`), `capabilityOverrides?`, `requestTimeoutMs?` |\n| `pools`        | `orchestrator: [ids]`, `compact?: [ids]`, `regular?: [ids]`, `panel: { name: [ids] }`                                                                                                                  |\n| `routing`      | `mode` (`single`/`smart`/`all`), `defaultPanel`, `forceSingleWhenTools`, `imageFallback`, `smart{embeddings,tiers,thresholds,fallbackTier}` — see [docs/ROUTING.md](docs/ROUTING.md)                   |\n| `caching`      | `anthropic{enabled,maxBreakpoints,oneHour}`, `promptCacheKey{enabled}`, `sessionAffinity{enabled}`                                                                                                     |\n| `capabilities` | `refreshIntervalSec`                                                                                                                                                                                   |\n\nEnvironment overrides: `FUSION_PORT`, `FUSION_HOST`, `FUSION_AUTH_KEY`, `FUSION_CONFIG`, `FUSION_HOME`, `CODEX_HOME`, `FUSION_LOG_LEVEL` (`debug` for full lifecycle), and embedder knobs `FUSION_EMBED_MODEL`, `FUSION_EMBED_DTYPE`, `FUSION_EMBED_DEVICE` (`auto` uses GPU if available), `FUSION_EMBED_CACHE`. See [docs/LOGGING.md](docs/LOGGING.md).\n\n---\n\n## Limitations\n\n- **The panel is text-only (no tool-call aggregation).** The panel fans out and synthesizes one answer via a judge; it cannot merge heterogeneous tool calls. So **any request carrying `tools` is automatically routed `single`, never the panel** — even under `mode: all` / `x-fusion-route: all` / `x-fusion-tier: plan`. This is what makes agentic coding (Claude Code) work over many turns; without it the agent would receive prose with no `tool_use` and stall. Tool-free requests still fuse via the panel. Multi-model reasoning _does_ reach tool turns via **council-then-act** (advisors deliberate as text → the actor runs with the real tools + their briefing); see [docs/ROUTING.md](docs/ROUTING.md).\n- **Smart mode needs the embedding model.** First run downloads it (~100–160 MB) to the HF cache; if it can't load, smart requests 503 (by design — no silent fallback). Use `single`/`all` offline.\n- **No config hot-reload.** Edit `config.yaml`, then `fusion restart`.\n- **No cost optimiser / eval harness** yet.\n- **Panel latency**: panel members run non-streaming and are buffered before the judge streams; the first token appears only once the judge starts.\n\n---\n\n## Run the tests\n\n```bash\npnpm install\npnpm test          # builds, then runs node --test\npnpm run check-all # prettier --check + eslint + tsc --noEmit\n```\n\n---\n\n## For AI agents\n\nMachine-readable setup:\n\n```yaml\nprovider: openai-compatible # also exposes an anthropic-compatible surface\nbase_url: http://localhost:8787/v1 # Anthropic clients: http://localhost:8787\napi_key: test # any value unless server.authKey is set\nmodels: [fusion]\nopenai_endpoints: [GET /v1/models, POST /v1/chat/completions]\nanthropic_endpoints: [POST /v1/messages, POST /v1/messages/count_tokens, GET /v1/models]\nroute_override:\n  body: { extra_body: { fusion_route: \"single|smart|all|compact|regular|plan\", panel: \"\u003cname\u003e\" } }\n  header:\n    {\n      x-fusion-route: \"single|smart|all|compact|regular|plan\",\n      x-fusion-tier: \"…\",\n      x-fusion-panel: \"\u003cname\u003e\",\n    }\ndebug_header: x-fusion-route # on every response: mode, tier, served_by, scores, reason\nstreaming: supported # single/tier route + panel judge synthesis\n```\n\nTo install and run autonomously: `npm i -g @dymoo/fusion`, write a `config.yaml` (copy `config.example.yaml`), then `fusion start`. Probe `GET /health` for readiness (it reports upstreams, pools, the capability floor, and the smart-classifier status). Routing internals: [docs/ROUTING.md](docs/ROUTING.md); logs: [docs/LOGGING.md](docs/LOGGING.md).\n\n---\n\n## License\n\nMIT © dymoo\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdymoo%2Ffusion","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdymoo%2Ffusion","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdymoo%2Ffusion/lists"}