Metadata-Version: 2.4
Name: ficelle-router
Version: 0.3.6
Summary: Local OpenAI-compatible strict-zero model router for AI agents
License-Expression: BUSL-1.1
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cryptography>=42
Requires-Dist: packaging>=24
Requires-Dist: requests>=2.31
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Dynamic: license-file

# Ficelle

Ficelle is a standalone local OpenAI-compatible strict-zero model router for agent clients. Codex, Cursor, Continue, Open WebUI, Hermes, and custom scripts all point at the same local endpoint — no host application is required ([one recipe per client](docs/clients/README.md)). Claude Code speaks the Anthropic protocol and cannot point at Ficelle directly yet; [its page](docs/clients/claude-code.md) says what works today. Hermes is one optional, first-class integration among them: where it is installed, it sees Ficelle as one of its LLM providers.

It exposes `http://127.0.0.1:8646/v1` with stable virtual models such as `ficelle/auto-orchestrator`, `ficelle/auto-tools`, `ficelle/auto-json`, `ficelle/auto-compression`, `ficelle/auto-long`, `ficelle/auto-fast`, and capability-specific virtual models for coding, reasoning, multimodal, vision, video, and audio routing. `ficelle/auto-coding` fails closed unless Ficelle has published a current signed certification for the exact provider deployment.

## Current status

Ficelle is at `v0.3.6`: usable for local real-use, installable from a pinned bootstrap or from PyPI, and deliberately conservative about provider costs.

Solid today:

- local OpenAI-compatible `/v1/models` and `/v1/chat/completions` endpoints;
- strict-zero default routing: free pricing required, tools required by default, no paid fallback;
- OpenRouter and Nous/RMS provider support;
- NVIDIA NIM, Mistral, Groq, Gemini (Google AI Studio), and Cerebras as credential-gated `free_quota` providers (enabled but dormant without a key), not strict-zero;
- Cavoti as a credential-gated `free_quota` relay (four tier entries — `cavoti_basic`, `cavoti_gpt_premium`, `cavoti_claude_plus`, `cavoti_claude_premium`, EU/Germany node) that resells paid frontier models (Claude/GPT/DeepSeek/GLM/Grok) under a free Pro quota — a grey-market relay, not a first-party free tier and not strict-zero, dormant without a key. The tiers share one daily dollar pool (`shared_account` scope) and carry a per-tier `burn_weight` so routing prefers the cheapest tier;
- SiliconFlow as a credential-gated `free_quota` provider exposing one genuinely-free model (`nex-agi/Nex-N2-Pro`, 262K context, tools) through an id allowlist (its catalog ships no pricing) — an independent free quota that aggregates with OpenRouter for the same model; not strict-zero, dormant without a key;
- Ollama Cloud as a credential-gated `free_quota` provider admitting its cloud catalogue without an allowlist, with runtime model-only quarantine when a listed model becomes subscription-only — preview quota, not strict-zero, dormant without a key;
- GitHub Models as a `free_quota` integration that is wired and tested but **dormant — not registered** in the default config (absent from the UI, routes nothing): its free tier hard-caps request input at 8000/4000 tokens, so no free model reaches the 128k usable-input floor; the definition (`GITHUB_MODELS_PROVIDER_DEF`) stays in the tree, one edit from activation if GitHub raises the cap;
- OpenCode Zen as a credential-gated `free_model` provider exposing only explicit allowlisted FreeModel IDs (`deepseek-v4-flash-free`, `mimo-v2.5-free`, `nemotron-3-ultra-free`, `north-mini-code-free`), like OpenRouter free models rather than provider-wide FreeQuota, dormant without a key;
- Kilo Code (Kilo AI Gateway) as a credential-gated `free_model` provider on a non-standard path (`/api/gateway`, no `/v1`); the free pool requires both the provider's `isFree=true` marker and strict-zero catalog pricing, with no allowlist to maintain (`provider_free_catalog_pricing` proof — disagreements, paid, and sparse rows fail closed); never strict-zero, dormant without a key;
- Cohere (OpenAI Compatibility API) as a credential-gated `free_quota` provider (dormant without a key) exposing the Command chat models that support tools and ≥128K context (its `/v1/models` ships no pricing/context, so a per-provider id allowlist scopes the free pool, like SiliconFlow); not strict-zero;
- Naraya / NaraRouter (OpenAI-compatible gateway) as a credential-gated `free_quota` provider (dormant without a key), a third-party reseller router whose sparse `/v1/models` ships no pricing, so a mandatory id allowlist (`require_model_id_allowlist`, fail-closed) scopes the free pool to five fast open-weight chat models (the resold Claude/GPT/Gemini are excluded as unusably throttled); `free_scope: provider` (one 5M-tokens/day quota); not strict-zero;
- OrcaRouter (`free_model`) as a credential-gated official free-id pool: every catalog id ending in `-free`, plus `orcarouter/free`. Paid Claude/GPT rows and sparse fusion aliases stay out; no hand-maintained allowlist. Hetzner Inference is registered as credential-gated `free_quota`; Ficelle follows its definitive authenticated catalog without a hard-coded model list, while capability and context filters still fail closed;
- macOS LaunchAgent and Linux `systemd --user` lifecycle through the `ficelle` CLI;
- conservative setup wrapper through `scripts/install-ficelle.py` and `ficelle-setup`;
- Hermes provider plugin template for the `ficelle` provider;
- admin UI and machine-readable `/admin/status.json`;
- request routing logs, last-route state, provider/model errors, cooldowns, quarantine, benchmarks, canaries, audit log, and virtual model rollback;
- per-request token accounting with a windowed savings estimate on the admin Requests page (`Spent $0.00` under strict-zero, `Est. saved` at the routed models' own paid-sibling rates, recorded at catalog refresh);
- default-off native context compression controls with dry-run reporting, local CCR retrieval, admin observability, and live-zone limited to low-risk real-use pending evidence.

Deliberately not done yet:

- no paid fallback;
- no quota-based providers enabled by default;
- no native macOS app before the CLI/package path is stable.

## Architecture at a glance

```text
OpenAI-compatible client / optional Hermes integration
          |
          v
http://127.0.0.1:8646/v1
          |
          v
Ficelle router
  |- config + virtual profiles
  |- strict-zero catalog filtering
  |- provider credential resolution
  |- model scoring and fallback
  |- cooldowns / quarantine / failure classification
  |- benchmarks / canaries / verified capability state
  `- admin API + dashboard + logs
          |
          v
OpenRouter / Nous / future gated providers
```

Runtime state lives under `~/.ficelle/` by default, independently of any client integration. Provider secrets are resolved from the environment / Ficelle keychain (`~/.ficelle/ficelle-secrets.keychain-db` on macOS) and must never be committed.

## Repository layout

```text
.
├── AGENTS.md                         # operating rules for autonomous coding agents
├── CLAUDE.md                         # Claude Code entrypoint, points to AGENTS.md
├── README.md                         # this overview
├── config.example.json               # safe example runtime config
├── ficelle_router.py                 # legacy compatibility entrypoint
├── scripts/install-ficelle.py        # source-checkout setup wrapper
├── scripts/compression-dogfood-report.py # local compression real-use metrics
├── src/ficelle/
│   ├── cli.py                        # ficelle CLI and LaunchAgent lifecycle
│   ├── install.py                    # ficelle-setup package installer
│   ├── router.py                     # HTTP router, admin UI/API, selection engine
│   ├── service.py                    # platform service backend abstraction
│   └── assets/hermes-plugin/         # packaged Hermes plugins
├── plugins/ficelle-compression/      # source Hermes retrieval tool plugin
├── plugins/model-providers/ficelle/  # source Hermes provider plugin template
├── skills/ficelle-router/            # Hermes skill for operating Ficelle
├── tests/                            # pytest regression suite
└── docs/                             # product, architecture, PRDs, runbooks
```

## Install for local development

```bash
cd ~/Projets/ficelle
python -m pip install -e '.[dev]'
```

Run local checks:

```bash
python -m pytest -q
python -m compileall src ficelle_router.py
```

## Standalone setup

Ficelle needs Python 3.11+, but it does not require Hermes. The open Core is
published as `ficelle-router`:

```bash
uv tool install ficelle-router      # or: pip install ficelle-router
ficelle-setup --skip-package --target generic
```

Run interactively, setup ends by offering to paste your OpenRouter key (hidden input;
routing uses your own key — nothing serves without one) and then runs `ficelle demo`,
so the first routed completion happens inside the install session. Scripted or
declined, the equivalent manual steps are:

```bash
ficelle set-key openrouter   # or pipe it: ficelle set-key openrouter --stdin
ficelle doctor --text
ficelle health
ficelle models
```

For end users the pinned bootstrap in [`docs/install.md`](docs/install.md) stays
the recommended path: it is the only one that verifies the wheel it installs
against a SHA-256 pinned in the script. An authorized source checkout also works:

```bash
git clone https://github.com/TheBlueHouse75/ficelle.git
cd ficelle
python scripts/install-ficelle.py --target generic
```

Then point any OpenAI-compatible client at `http://127.0.0.1:8646/v1`:

```python
from openai import OpenAI

client = OpenAI(
    base_url="http://127.0.0.1:8646/v1",
    api_key="ficelle-local",
)
```

The public bootstrap installs the private Ficelle Pro wheel when a licence key is present:

```bash
(
  read -s FICELLE_LICENSE_KEY
  export FICELLE_LICENSE_KEY
  curl -fsSL https://raw.githubusercontent.com/TheBlueHouse75/ficelle-open-core/v0.3.6/scripts/bootstrap-ficelle.py | python3 - --target generic
)
```

That command does not require cloning this repo and keeps the key out of argv and shell history. The bootstrap downloads the private wheel, selects a target, installs into a target-aware Python, and runs the packaged setup flow. `--target auto` is the default: it uses the Hermes integration when a reliable local Hermes signal is found, and otherwise installs the generic standalone target. Use `--target generic` or `--target hermes` to make the launch target explicit.

Dry-run first when validating a new environment:

```bash
python scripts/install-ficelle.py --dry-run
python scripts/bootstrap-ficelle.py \
  --wheel-url ficelle-pro/dist/ficelle_pro-0.3.6-py3-none-any.whl \
  --target generic \
  --dry-run
```

After package installation, the same setup flow is available as:

```bash
ficelle-setup --skip-package --target generic
```

The setup wrapper starts with readable preflight checks, installs the package when requested, starts the selected local service backend, and runs `doctor`, `health`, and `models` smoke checks. It always passes `FICELLE_HOME` (default `~/.ficelle`) to the service; generic and OpenClaw targets do not inherit or create `HERMES_HOME`.

For a safer first pass on a new machine:

```bash
python scripts/install-ficelle.py --preflight-only
python scripts/install-ficelle.py --dry-run --target generic
```

Only the Hermes target installs the packaged provider/tool plugins. Hermes config remains opt-in: `--target hermes --configure-hermes` writes a ready snippet to `~/.hermes/ficelle/hermes-config.snippet.yaml`, creates `~/.hermes/config.yaml` only when absent, or updates an existing Ficelle-managed block with a timestamped backup. Existing unmanaged configs are left untouched. `ficelle-setup --target hermes --rollback` restores the latest available integration backups; paths without a backup are always left untouched.

When upgrading from the former layout, setup implicitly copies `~/.hermes/ficelle/` to `~/.ficelle/` only when no Ficelle home was explicitly selected and the destination is absent, empty, or contains only credential files. Existing credentials and the legacy source are preserved; a destination containing runtime data is left untouched.

Today, the managed service backends are macOS LaunchAgent, Linux `systemd --user`, and a Windows per-user Scheduled Task (no elevation required; provider secrets resolve through the Windows Credential Manager). The Windows and Linux backends still need clean-host validation before public release claims.

See [`docs/install.md`](docs/install.md) for source, wheel, dry-run, Hermes config, and verification details.

## CLI commands

```bash
ficelle install
ficelle start
ficelle stop
ficelle restart
ficelle status
ficelle refresh
ficelle health
ficelle models
ficelle doctor --json
ficelle canary
ficelle synthetic-health run --depth deep --json
ficelle export
```

`synthetic-health` is the deep, sequential user-view health harness for weekly reference-installation
runs. It exercises every configured virtual profile and every invokable strict-zero model through
the loopback API, then correlates responses with route logs and state. Scheduling is disabled until
explicitly installed. See [`docs/components/synthetic-health.md`](docs/components/synthetic-health.md).

Useful local endpoints:

```bash
curl -s http://127.0.0.1:8646/health
curl -s http://127.0.0.1:8646/admin/status.json | python -m json.tool
curl -s http://127.0.0.1:8646/v1/models | python -m json.tool
```

## Optional Hermes integration

Hermes is not required to run Ficelle. With `--target auto`, setup selects Hermes only when it detects a Hermes Python/runtime, CLI, config, or agent directory. `--target hermes` selects it explicitly, installs the Ficelle provider and compression plugins with idempotent backups, and passes `HERMES_HOME` only to that integration. The provider name is `ficelle`, with display label `Ficelle FREE`.

Export the recommended Hermes YAML:

```bash
ficelle export --target hermes
```

Setup makes Ficelle the main Hermes route through `ficelle/auto-orchestrator` and also installs
specialized auxiliary slots:

```yaml
model:
  provider: "custom"
  base_url: "http://127.0.0.1:8646/v1"
  model: "ficelle/auto-orchestrator"
auxiliary:
  title_generation:
    provider: "ficelle"
    model: "ficelle/auto-fast"
  compression:
    provider: "ficelle"
    model: "ficelle/auto-compression"
  web_extract:
    provider: "ficelle"
    model: "ficelle/auto-json"
```

`ficelle/auto-compression` is the Hermes compaction slot. `ficelle/auto-long` means large-context routing and is intentionally not the default compression recommendation.

## Experimental OpenClaw integration

`ficelle-setup --target openclaw` installs the standalone service without Hermes files or environment. OpenClaw integration is experimental: review and merge the export from `/admin/export/openclaw` manually.

## Documentation

Start with [`docs/README.md`](docs/README.md).

Key docs:

- [`docs/install.md`](docs/install.md) — source/wheel setup and verification.
- [`docs/components/router.md`](docs/components/router.md) — routing engine, provider policy, fallback, state.
- [`docs/components/cli-and-service.md`](docs/components/cli-and-service.md) — CLI, setup wrapper, service lifecycle.
- [`docs/components/service-backends.md`](docs/components/service-backends.md) — service backend abstraction, macOS LaunchAgent, Linux systemd user service, unsupported platforms.
- [`docs/components/hermes-integration.md`](docs/components/hermes-integration.md) — provider plugin and Hermes config.
- [`docs/components/admin-and-observability.md`](docs/components/admin-and-observability.md) — admin UI/API, status, logs, audit, dashboard.
- [`docs/components/config-and-state.md`](docs/components/config-and-state.md) — config and runtime state files.
- [`docs/testing-and-verification.md`](docs/testing-and-verification.md) — local checks, live smokes, release validation.
- [`docs/features-next-steps.md`](docs/features-next-steps.md) — current tracking file and remaining work.
- [`docs/ficelle-prd.md`](docs/ficelle-prd.md) — umbrella product PRD.
- [`docs/prds/README.md`](docs/prds/README.md) — focused PRD index.

## Provider policy

Enabled by default (strict-zero catalog):

- OpenRouter;
- Nous/RMS.

Enabled but credential-gated `free_quota` (dormant without a key, never strict-zero):

- NVIDIA NIM;
- Mistral. Unlike NVIDIA NIM, Mistral bills the same model IDs if the account carries a billing budget, so keep the account on the Experiment plan with no budget; the anti-false-free guard quarantines any model the runtime reports as billed.
- Groq. Same posture as Mistral: bills the same model IDs if the account carries a billing budget, so keep the account on the free tier with no budget; its free tier is rate-limited (~30 RPM), so expect more rate-limit cooldowns.
- Gemini (Google AI Studio). Same billing posture (free tier, no budget) and a tighter rate limit (~15 RPM, 1500 RPD on Flash). Off unless `GEMINI_API_KEY` is configured.
- SiliconFlow. An OpenAI-compatible aggregator whose `/v1/models` ships no pricing, so a per-provider id allowlist restricts the free pool to the one genuinely-free model (`nex-agi/Nex-N2-Pro`, 262K context, tools). An independent free quota that aggregates with OpenRouter's copy of the same model. Keep the account with no paid top-up; the anti-false-free guard quarantines any model the runtime reports as billed. Off unless `SILICONFLOW_API_KEY` is configured.
- Ollama Cloud. An OpenAI-compatible endpoint (`https://ollama.com/v1`, **not** `/api/v1`) whose `/v1/models` is admitted as the cloud free-quota catalogue (`free_scope: provider`, no allowlist). A listed model can later require a subscription: HTTP 403 `requires a subscription` / `upgrade for access` quarantines only that model, raises a Control Center notice, and does not pause the rest of Ollama. The account-wide preview quota remains unpublished and unstable, and Ollama meters it by GPU time, not tokens — generic quota cooldown/probe recovery handles exhaustion. **Open point:** commercial use of the free tier is not confirmed in Ollama's ToS. Off unless `OLLAMA_API_KEY` is configured.
- Cohere (OpenAI Compatibility API). Its Compatibility `/v1/models` is OpenAI-shaped but sparse (no pricing or context) and mixes chat models with embeddings, rerank, transcribe, and vision, so a per-provider id allowlist restricts the free pool to the Command chat models that support tools and ≥128K context (`command-a-03-2025`, `command-a-plus-05-2026`, `command-a-reasoning-08-2025`, `command-r-08-2024`, `command-r-plus-08-2024`, `command-r7b-12-2024`, `command-r7b-arabic-02-2025`, `north-mini-code-1-0`; verified live 2026-06-21). Keep the account with no paid budget; the anti-false-free guard quarantines any model the runtime reports as billed. The free tier is rate-limited (~20 RPM, ~1000 calls/month), so expect more rate-limit cooldowns. Off unless `COHERE_API_KEY` (or `CO_API_KEY`) is configured.
- Naraya / NaraRouter (OpenAI-compatible gateway, `https://router.naraya.ai/v1`). A third-party reseller router (like OpenRouter) that resells premium models at low IDR prices; its `/v1/models` is sparse with no pricing, so a mandatory per-provider id allowlist (`require_model_id_allowlist`, fail-closed) restricts the free pool to five open-weight chat models confirmed live 2026-06-21 to answer fast (1-5s) with tools and ≥252K context (`mistral-large`, `mistral-medium-3-5`, `minimax-m3`, `deepseek-v4-flash-naraya`, `qwen3.7-max-naraya`). The resold premium models (Claude/GPT/Gemini) are excluded: live they were throttled to a 22-43s time-to-first-byte, unusable for an agent. One 5M-tokens/day quota is shared account-wide (`free_scope: provider`), and no quota headers are exposed (consumption is visible only on the Naraya dashboard). Create the key with no credit card so the free pool cannot spill into paid pay-as-you-go. Off unless `NARAYA_API_KEY` is configured.

Enabled but credential-gated `free_model` (dormant without a key, model allowlist only):

- OpenCode Zen. An OpenAI-compatible gateway whose `/zen/v1/models` catalog is sparse and whose Free models are model-specific, not provider-wide quota. Ficelle routes only explicit allowlist entries: `deepseek-v4-flash-free`, `mimo-v2.5-free`, `nemotron-3-ultra-free`, and `north-mini-code-free` after a live smoke on 2026-06-20. Ended free promotions are quarantined model-scoped. Off unless `OPENCODE_ZEN_API_KEY` is configured.
- Kilo Code (Kilo AI Gateway). An OpenAI-compatible aggregator on a non-standard path (`/api/gateway/chat/completions`, no `/v1`). Its rich catalog exposes per-model pricing and an `isFree` flag, so Ficelle requires both signals to agree: `isFree=true` and strict-zero prompt/completion pricing. There is no allowlist to maintain; a newly-added model routes with no code change only when both provider signals agree and the normal tools/context gates pass. Rows marked paid, sparse-pricing rows, and contradictions such as `isFree=false` with `$0` pricing fail closed. The dedicated `provider_free_catalog_pricing` proof keeps the provider FreeModel (a credential-gated opt-in outside the default strict-zero pool) while retaining strict-zero pricing evidence. Paid or billable routes return `HTTP 402` at zero balance and are quarantined model-scoped; keep the account at 0 credits so paid/flipped models fail closed. Off unless `KILO_API_KEY` is configured. Reverified 2026-08-21 after `stealth/ox-alpha` exposed `$0` pricing with `isFree=false` and returned 402 while an `isFree=true` control route succeeded; the `:free` models' commercial-use rights are unconfirmed.

Wired but **not registered** (dormant, absent from the admin UI, routes nothing):

- GitHub Models. The integration is complete and tested but is deliberately not registered in the default config, because its free tier hard-caps request **input** at 8000 tokens (low/high tier) or 4000 (custom tier) and 403s the top reasoning models (o1/o3) — verified live 2026-06-21 — so **no free model reaches the 128k usable-input floor**, even though its non-standard `/catalog/models` reports each model's much larger native window. The provider definition (`GITHUB_MODELS_PROVIDER_DEF`) and its support code (non-standard catalog fetcher, credential path, tests) stay in the tree so it is one edit away from activation if GitHub raises the cap. Tools are confirmed working; auth would be a GitHub PAT scoped `models:read` (`GITHUB_MODELS_API_KEY`; the ambient `GITHUB_TOKEN` is intentionally not auto-resolved).

Rules:

- default routing must never create silent spend;
- `allow_paid_fallback` stays `false` unless explicitly changed and documented;
- provider keys stay in the environment / Ficelle keychain, never repo files;
- auth diagnostics are presence-only and redacted;
- catalog claims must be separated from benchmark/canary-verified capability state.

## Security

- No secrets in this repository.
- No provider keys in config, state fixtures, docs, screenshots, or logs committed to git.
- Runtime state lives under `~/.ficelle/`, outside the repo.
- Route logs should contain request IDs, selected models, statuses, reasons, latency, and redacted details, not prompts or credentials.
- Admin surfaces are local-only for the MVP.

## Development workflow

Read [`AGENTS.md`](AGENTS.md) before changing code. The short version:

1. Inspect existing code and docs before editing.
2. Keep the diff minimal.
3. Update docs with behavior changes.
4. Run relevant tests/checks.
5. Verify `git diff --check` and `git status` before handing off.
6. Do not push without explicit approval.
