{
  "markdown": "# ask-fable: Multi-Model Reasoning MCP Server\n\n<!-- mcp-name: io.github.baggybin/ask-fable -->\n\n<p align=\"center\">\n  <img src=\"images/ask_fable_hero_banner.jpg\" alt=\"ask-fable — The AI Oracle Council: Ultra-dense architectural hero banner\">\n</p>\n\n**ask-fable** is a portable, installable **MCP (Model Context Protocol) server** that equips AI coding agents in **Claude Code**, Cursor, Codex, and OpenCode with guarded code and architecture reasoning from **Anthropic's Claude Fable** (the newest `claude-fable-*`), Claude Opus 5 (`claude-opus-5`), MiniMax (`MiniMax-M3`), Gemini, Codex, GLM, DeepSeek, Grok, Kimi, and Ollama Cloud models. It can query one backend, synthesize a parallel council, run an ordered refinement chain, or stage a structured adversarial debate.\n\nFable and Opus 5 use Claude Code's existing OAuth session (through the Agent SDK,\nwith the `claude` CLI as a fallback). MiniMax, Gemini, Codex, Grok, and local Ollama\nsimilarly reuse authenticated local CLIs. GLM, DeepSeek, and Atlas Cloud are\noptional HTTP backends that need server-side API keys.\n\n## Start here\n\n| If you need to… | Use |\n|---|---|\n| Ask one trusted coding model, with follow-up memory | `ask` (Fable) / `ask_opus5` (Opus 5) |\n| Compare independent answers in parallel | `ask_council` |\n| Draft, critique, then decide in order | `ask_chain` |\n| Stress-test a high-impact decision | `ask_debate` |\n| Select a task-matched Atlas Cloud model | `list_atlas_models` → `ask_atlas` |\n| Atlas council with GPT-5.6 Sol adjudicating | `ask_atlas_council` |\n| Reuse large code context without pasting it again | `context_write` + `context_ref` |\n| Investigate a request after it ran | `trace_list` + `trace_get` |\n\n**Start with `ask` for one hard question.** Escalate to a council, chain, or\ndebate only when the decision warrants the extra latency and cost.\n\n## What it gives you\n\nask-fable gives an MCP client four ways to reason:\n\n| Mode | What happens | Best for |\n|---|---|---|\n| **Ask** | One model answers directly; Fable can remember a session | Everyday debugging and design questions |\n| **Council** | Several models answer in parallel; Fable reconciles them | Comparing independent opinions |\n| **Chain** | Models work in order: draft → critique → decide | Deliberate refinement and cost-tiered escalation |\n| **Debate** | A proposer and opponent test claims; Fable adjudicates | Contentious, hard-to-reverse decisions |\n\nThe same guard, context bus, cache, audit trail, and tracing layer wrap every\nmode. Backends are optional: use Fable alone, call a specific provider, or mix\nFable, MiniMax, Gemini, Codex, Grok, GLM, DeepSeek, Ollama, and Atlas Cloud.\nUnavailable council members are reported and skipped instead of failing the\nwhole request.\n\nThe cheapest real second opinion is the **`twin`** token — the *twin flames*.\nIt expands to **both Anthropic reasoners at once, Fable + Claude Opus 5**, and\nboth ride the same OAuth session as `ask`, so a two-model cross-check costs you\nno provider keys and no extra setup:\n\n```python\nask_council(models=[\"twin\"])        # or tier=\"twin\" — the pair, in parallel\nask_chain(pipeline=\"m3 > twin\")     # cheap draft, then fable → opus in turn\n```\n\nFive features make the result useful to an agent, not just readable by a human:\n\n- **Structured sidecar** — every answer carries a machine-readable\n  `sidecar` (`{recommendation: apply|investigate|reject|needs_more_context,\n  confidence, needs_context}`) next to the prose, so an agent acts on it directly.\n  When the model needs more, a **`followup`** tells it exactly what to paste, and a\n  per-session terminator stops an unbounded re-ask loop (`status:\"context_exhausted\"`).\n- **Context bus** — `context_write` a big codebase context ONCE under a key, then\n  pass **`context_ref`** on any ask tool (or council) to pull it in instead of\n  re-pasting. Shared by every agent on the server; `context_read` / `context_list` /\n  `context_delete` round it out.\n- **Council consensus** — councils return a `consensus` signal\n  (`strong` | `partial` | `divergent` | `unknown`) + `material_disagreement` computed\n  from the panel's recommendations, each `sources` entry shows that model's\n  `recommendation`, and the synthesis is **anonymized** (Expert A/B, Fable last) to\n  blunt self-preference bias.\n\n- **Correlated traces** — every call includes a `trace_id`; inspect the ordered\n  request timeline without storing raw prompts in the default safe mode.\n- **Session hub** — successful turns from local MCP instances are mirrored into a\n  shared, visibility-only dashboard. Agents can use the same label to coordinate\n  work without that shared history ever becoming model context.\n\n## How it works\n\n<p align=\"center\">\n  <img src=\"images/ask-fable-system-map.png\" alt=\"ask-fable system map: an MCP client passes context through the guard and router to single, council, chain, or debate modes backed by multiple model providers\">\n</p>\n\nA request enters through MCP, resolves any reusable `context_ref`, passes the\nguard, and is routed to the chosen reasoning mode. The result is normalized into\nan answer plus a machine-readable sidecar, persisted to the configured\nobservability stores, and returned with a trace ID.\n\n<p align=\"center\">\n  <img src=\"images/ask-fable-request-flow.png\" alt=\"ask-fable request lifecycle: receive, resolve context, guard, cache lookup, run mode, normalize, persist, and return\">\n</p>\n\nThe project ships its own two-layer request gate: a size/sanity floor followed\nby a prohibited-use denylist. Fable's model prompt adds the final semantic scope\ncontract. See [The guard](#the-guard) for the exact behavior.\n\n## The guard\n\n<p align=\"center\">\n  <img src=\"images/guard_shield_new.jpg\" alt=\"A high-tech digital shield blocking malicious code\">\n</p>\n\n<p align=\"center\">\n  <img src=\"images/guard_layers_modern.jpg\" alt=\"Three-layer guard before any model call: (1) sanity floor, (2) prohibited-use denylist, (3) model scope contract\">\n</p>\n\nEvery question is checked **before any model call**:\n\n1. **Sanity floor** — rejects only empty / too-short (`<3` chars) / too-long\n   (`>65536` chars) questions. Context is **unbounded** by default (any cap you set\n   is floored to 512,000 chars). Breadth is **allowed**.\n2. **Prohibited-use denylist** — ask-fable's bundled offensive-security and\n   biology dual-use patterns. Extend it via\n   `ASK_FABLE_DENYLIST_FILE` (one term per line). Benign multi-word phrases\n   (e.g. `request payload`) are neutralized *before* matching so an ambiguous\n   word like `payload` used in an ordinary engineering sense doesn't false-trip;\n   add your own via `ASK_FABLE_ALLOWLIST_FILE` (one phrase per line). This only\n   rescues the exact benign phrase — a bare prohibited term still rejects.\n3. **Model scope contract** — Fable answers engineering questions, including\n   conceptual/brainstorming ones with no code context (breadth is fine), and\n   replies `REFUSED: <reason>` only when the question itself directly asks for\n   offensive-security work (exploit development, attack tooling) or non-software\n   domain knowledge (e.g. biology). Questions about security-related code are\n   normal engineering.\n\nEvery decision is appended to an owner-only JSONL audit log (question hashed by\ndefault; `ASK_FABLE_AUDIT_RAW=1` to store raw).\n\n## Quick start\n\n### 1. Install\n\n> **New here?** The [setup & usage guide](docs/GUIDE.md) walks through install,\n> registering with Claude Code, setting up every backend (API keys, Ollama Cloud,\n> MiniMax/Gemini CLIs), `/mcp` verification, and how to use every tool.\n>\n> **Want the big picture?** The [visual architecture map](docs/architecture.html)\n> charts the whole server end to end — the request pipeline,\n> the oracle bridges, council/chain orchestration, and on-disk state.\n\n```bash\n# not on PyPI yet — install from source:\npip install -e .\n# or with pipx:\npipx install .\n```\n\nRequires the Claude Code CLI to be installed and logged in (that's the OAuth\nsession Fable is reached through).\n\n### 2. Register in Claude Code\n\nAdd to `~/.claude/.claude.json` (root-owned — edit as the owner, e.g. via\n`sudo`):\n\n```json\n{\n  \"mcpServers\": {\n    \"ask_fable\": { \"command\": \"ask-fable\" }\n  }\n}\n```\n\n(or `\"command\": \"python3\", \"args\": [\"-m\", \"ask_fable\"]`). Restart Claude Code;\nall 37 `mcp__ask_fable__*` tools become available to the client. They are grouped\ninto reasoning modes, direct provider calls, context management, configuration,\nand observability; see the [tool guide](#tool-guide) for the short chooser or\n[`CLAUDE.md`](CLAUDE.md) for the complete one-line inventory.\n\n### opencode — `~/.config/opencode/opencode.json`\n\n> **Using opencode?** The [`docs/OPENCODE.md`](docs/OPENCODE.md) guide walks\n> through the full setup — the exact schema-valid MCP block, optional API keys,\n> the restart-to-load behavior, and troubleshooting. The snippet below is the\n> minimal registration.\n\n```json\n{\n  \"mcp\": {\n    \"ask_fable\": {\n      \"type\": \"local\",\n      \"command\": [\"ask-fable\"],\n      \"enabled\": true\n    }\n  }\n}\n```\n\n### 3. Ask a question\n\nIn your MCP client, call `ask` with a focused question and the relevant code or\nerror. Reuse the same `session` key for follow-ups:\n\n```json\n{\n  \"question\": \"Why does this cache invalidate too early?\",\n  \"context\": \"<relevant code and failing test output>\",\n  \"session\": \"cache-investigation\"\n}\n```\n\n## Tool guide\n\nThe server exposes 37 MCP tools. You only need to remember four entry points:\n`ask`, `ask_council`, `ask_chain`, and `ask_debate`. Everything else selects a\nspecific backend, manages reusable context, or inspects what happened.\n\n> **Quick menu:** [`CLAUDE.md`](CLAUDE.md) lists all 37 tools grouped by purpose\n> (core reasoning · single models · context bus · ops & observability), one line\n> each — a fast lookup without the full prose below.\n\n| Goal | Start with | Escalate when |\n|---|---|---|\n| Solve or debug one problem | `ask` (Fable) or `ask_opus5` (Claude Opus 5 — ~half the price, faster) | use `context_ref` for large reusable context |\n| Get one alternate opinion | `ask_m3`, `ask_deepseek`, `ask_glm` (cheap direct APIs first), `ask_gemini`, `ask_codex`, `ask_grok`, `ask_kimi`, `ask_ollama`, `ask_atlas`, or `ask_openrouter` (~400 models, one key) | use a council when you need comparison |\n| Pick an Atlas model for a task | `list_atlas_models(task=\"…\")` | call `ask_atlas` with the accepted selection or rendered picker |\n| Cross-check with a second strong model | `ask_council(models=[\"twin\"])` — Fable + Opus 5 on one OAuth session, no keys | add a third voice with `models=[\"twin\",\"m3\"]` |\n| Compare several views | `ask_council` | use `ask_chain` when order matters |\n| Cross-check Atlas models, GPT adjudicating | `ask_atlas_council` | pin the panel with `configure_atlas_council` |\n| Make a contentious decision | `ask_debate` | keep the scope narrow; it is the most expensive mode |\n| Inspect what happened | `trace_list` then `trace_get` | enable full mode only when redacted content is needed |\n\n### The four reasoning functions\n\n| Function | Mental model | Runs | Returns |\n|---|---|---:|---|\n| `ask` / `ask_opus5` | One expert with memory | One Fable / Opus 5 call | Answer, sidecar, follow-up hints |\n| `ask_council` | Independent panel, then synthesis | Parallel + synthesis | Merged answer, sources, consensus |\n| `ask_chain` | Draft → critique → decision | Sequential | Final answer, stages, recommendation drift |\n| `ask_debate` | Claim → challenge → ruling | Sequential, adversarial | Ruling, claim ledger, resolution |\n\nStart with `ask`. Choose a council when independence matters, a chain when order\nmatters, and a debate only when the disagreement itself needs to be tested.\n\n<details>\n<summary><strong>Complete function reference</strong> — parameters, providers, fallbacks, and response details</summary>\n\nThe sections below are the exhaustive reference. For a guided walkthrough with\ncopyable examples, use the [setup and usage guide](docs/GUIDE.md).\n\n### Single-model reasoning\n\n- **`ask(question, context=\"\", context_ref=None, session=\"default\", reset=false)`** —\n  guarded reasoning from **Fable**. Reuse the same `session` key for follow-ups (Fable\n  keeps context server-side); a new key or `reset=true` starts a fresh topic. Pass\n  **`context_ref`** (a key or list of keys stored with `context_write`) to pull big\n  context in by reference instead of re-pasting. The result carries a `sidecar`; when\n  the model wants more it returns a `followup` telling you what to paste and to re-ask\n  on the same session (with `likely_already_pasted` flagging what's probably already\n  there). All ask tools accept `context` and `context_ref`.\n- **`ask_opus5(question, context=\"\", context_ref=None, session=\"default\", reset=false)`** —\n  the same tool on **Claude Opus 5 (`claude-opus-5`)**: identical arguments, identical\n  result shape, same multi-turn `session`/`reset` model, same Claude Code OAuth session\n  (no API key, nothing extra to configure). Opus 5 is roughly **half Fable's price and\n  faster**, so prefer it for high-volume or long back-and-forth work and keep `ask` for\n  the hardest calls; running both on one question is a cheap two-model cross-check.\n  Sessions are namespaced per tool — the same key on `ask` and `ask_opus5` is two\n  independent conversations, and `reset_session(session, model=\"opus5\")` clears this\n  one. Opus 5 is also the **`opus`** token (aliases `opus5`, `opus-5`) in every\n  multi-model mode: council member or `synthesizer`, chain stage, debate\n  proposer/opponent/`adjudicator`.\n- **`ask_m3(question, context=\"\")`** — the same guarded reasoning from **MiniMax\n  (`MiniMax-M3`)** on its own, independent of Fable. Single-turn. Returns\n  `{\"status\":\"ok\",\"model\":\"MiniMax-M3\",\"answer\":...}`.\n- **`ask_glm(question, context=\"\")`** — the same guarded reasoning from **GLM\n  (`glm-5.2`)** on its own, via Z.ai's Anthropic-compatible endpoint. Single-turn.\n  Requires `ASK_FABLE_GLM_API_KEY` on the server (returns\n  `{\"status\":\"error\",\"kind\":\"not_configured\",...}` otherwise). Returns\n  `{\"status\":\"ok\",\"model\":\"glm-5.2\",\"answer\":...}`.\n- **`ask_deepseek(question, context=\"\")`** — the same guarded reasoning from\n  **DeepSeek (`deepseek-v4-pro`)** on its own, via DeepSeek's Anthropic-compatible\n  endpoint. Cheap direct API — prefer it over pricier cloud models for a quick\n  independent opinion. Single-turn. Requires `ASK_FABLE_DEEPSEEK_API_KEY` on the\n  server (returns `{\"status\":\"error\",\"kind\":\"not_configured\",...}` otherwise).\n  Returns `{\"status\":\"ok\",\"model\":\"deepseek-v4-pro\",\"answer\":...}`.\n- **`ask_gemini(question, context=\"\")`** — the same guarded reasoning from\n  **Google Gemini (`Gemini 3.1 Pro (High)`)** on its own, via the\n  already-authenticated local `agy` CLI (no API key set by the server — like\n  `mmx`). Single-turn. Requires the `agy` CLI installed and signed in (returns\n  `{\"status\":\"error\",\"kind\":\"binary_missing\",...}` otherwise). Returns\n  `{\"status\":\"ok\",\"model\":\"Gemini 3.1 Pro (High)\",\"answer\":...}`.\n- **`ask_codex(question, context=\"\")`** — the same guarded reasoning from\n  **OpenAI (`gpt-5.6-sol`)** on its own, via the already-authenticated local\n  `codex` CLI in non-interactive `codex exec` mode (no API key set by the server —\n  like `mmx`/`agy`). Runs hermetically and read-only — it can't see your repo, so\n  put the code it needs in `context`. Single-turn. Requires the `codex` CLI\n  installed and logged in (returns `{\"status\":\"error\",\"kind\":\"binary_missing\",...}`\n  otherwise). Returns `{\"status\":\"ok\",\"model\":\"gpt-5.6-sol\",\"answer\":...}`.\n- **`ask_grok(question, context=\"\", effort=None)`** — guarded, single-turn\n  reasoning from **Grok (`grok-4.6`)** through the already-authenticated local\n  `grok` CLI. The default low reasoning effort keeps context-heavy turns bounded;\n  override it with `effort` or `ASK_FABLE_GROK_REASONING`. Requires the `grok` CLI\n  installed and logged in (returns `{\"status\":\"error\",\"kind\":\"binary_missing\",...}`\n  otherwise). Returns `{\"status\":\"ok\",\"model\":\"grok-4.6\",\"answer\":...}`.\n- **`ask_kimi(question, context=\"\", effort=None)`** — guarded, single-turn\n  reasoning from **Kimi (`kimi-code/k3`)** through the local `kimi` CLI on your\n  Kimi Code subscription, sandboxed to pure text reasoning (no tools, no\n  filesystem). Prefer it over `ask_atlas` with `moonshotai/kimi-*`: same model\n  family, no Atlas key, no per-token billing. The CLI passes the prompt as one\n  argv value, which the kernel caps near 131k bytes, so an oversized prompt is\n  refused with `{\"status\":\"error\",\"kind\":\"context_too_large\",...}` pointing at\n  the HTTP route. Returns `{\"status\":\"ok\",\"model\":\"kimi-code/k3\",\"answer\":...}`.\n- **`ask_atlas(question, context=\"\", model=None, effort=None)`** — guarded,\n  single-turn reasoning from an **Atlas Cloud** text model (for example\n  `xai/grok-4.6` or `openai/gpt-5.6-sol`) over the OpenAI\n  `/v1/chat/completions` shape. Supports `quick`, `standard`, and `deep`\n  effort. It needs `ASK_FABLE_ATLAS_API_KEY` or `ATLASCLOUD_API_KEY`, except\n  `xai/grok-*` models route to the authenticated local `grok` CLI when present.\n  Returns `{\"status\":\"ok\",\"model\":\"...\",\"answer\":...}`.\n- **`ask_openrouter(question, context=\"\", model=None, effort=None)`** — guarded,\n  single-turn reasoning from any of **~400 OpenRouter models** (Anthropic, OpenAI,\n  Google, DeepSeek, Meta, Qwen, Moonshot, xAI, Mistral, …) behind **one API key**.\n  The catch-all for a model with no dedicated tool, and the cheapest way to compare\n  labs without configuring each provider. Call `list_openrouter_models` first — the\n  catalog is free. Needs `ASK_FABLE_OPENROUTER_API_KEY` (or `OPENROUTER_API_KEY`);\n  Grok and Kimi ids reroute to the local `grok`/`kimi` CLIs when installed.\n  Unlike Atlas, `effort` is clamped to what the chosen model actually supports —\n  OpenRouter publishes each model's reasoning efforts, so there is no wasted probe\n  request. The result reports the call's **real dollar cost**.\n- **`list_openrouter_models(refresh=true, task=\"\", limit=5, interactive=true)`** —\n  the live OpenRouter catalog with price per million, context window and per-model\n  reasoning support. Free (no key). `task=\"…\"` ranks a provider-diverse shortlist\n  from the catalog's own fields — reasoning support, context, price, release date —\n  so a model released today ranks correctly with no change here. A task mentioning\n  cheap/fast/high-volume flips the ranking toward the cheap and free tiers.\n- **`ask_openrouter_council(question, models=[], synthesizer=None)`** — a cross-lab\n  panel on one key, with the same GPT-first adjudicator ladder as\n  `ask_atlas_council`. **`configure_openrouter_council`** persists your panel.\n- **`list_atlas_models(refresh=true, task=\"\", limit=5, interactive=true)`** —\n  fetch the free live Atlas text-model catalog. With `task`, it ranks a\n  provider-diverse shortlist from the catalog's capability profiles, tags,\n  context window, latency, and pricing. On MCP clients that support form\n  elicitation it opens a native model + effort picker; otherwise it returns the\n  same choices under `picker` for the host to render. An accepted native choice\n  is returned as `selection: {action:\"accept\", model, effort}`. `limit` is 2–8\n  (default 5); `refresh:false` makes no network call and returns only effort\n  choices. The ranking is live metadata-based guidance, not an independent\n  benchmark.\n\n  You can simply ask your agent: *“Give me the best Atlas models for debugging a\n  large Rust repository.”* It should call\n  `list_atlas_models(task=\"debugging a large Rust repository\")`, show the picker,\n  and pass the accepted model and effort to `ask_atlas`.\n\n<p align=\"center\">\n  <img src=\"images/ask_atlas_new.jpg\" alt=\"Abstract high-tech visualization of an atlas, mapping out AI reasoning models in a cloud network\">\n</p>\n\n### Multi-model reasoning\n\n- **`ask_council(question, context=\"\", models=[\"fable\",\"minimax\"])`** — ask\n  **several models the same question in parallel**, then have Fable **synthesize**\n  their answers into one merged answer (reconciling conflicts on the merits). The\n  payload also returns each oracle's raw answer under `sources`. Single-turn.\n  Degrades to whichever oracle(s) answered, and only refuses/errors when none do.\n  `models` picks from **`fable`** (the newest Fable), **`fable51`** (claude-fable-5-1,\n  pinned), **`opus`** (claude-opus-5, same\n  OAuth session as Fable — always available), **`minimax`** (MiniMax-M3, via\n  the `mmx` CLI), **`gemini`** (Gemini 3.1 Pro, via the `agy` CLI), **`codex`**\n  (GPT-5.6 Sol, via the `codex` CLI), **`glm`** (GLM-5.2, via Z.ai's Anthropic\n  endpoint), and **`deepseek`** (deepseek-v4-pro, via DeepSeek's Anthropic\n  endpoint) — e.g.\n  `models=[\"fable\",\"opus\",\"minimax\",\"gemini\",\"codex\",\"glm\",\"deepseek\"]` for a\n  seven-model council.\n  `gemini` needs the local `agy` CLI; `glm` and `deepseek` require API keys\n  configured on the server (below); an\n  unconfigured or unreachable oracle is reported in `sources` and skipped, never\n  fatal. No provider keys are set by the server itself — each bridge reuses env\n  config or an already-authenticated CLI/session. You can also add **Ollama Cloud**\n  models as `ollama:<model>` tokens — e.g.\n  `models=[\"fable\",\"ollama:qwen3-coder:480b-cloud\",\"ollama:nemotron-3-ultra:cloud\"]`\n  (reached via your local `ollama` daemon by default — no key). Atlas Cloud\n  models work the same way with `atlas:<model-id>` tokens in councils, chains,\n  and debates. For a task-matched multi-model call, call\n  `list_atlas_models(task=\"review a risky database migration\")` first, then use\n  returned IDs such as\n  `models=[\"fable\",\"atlas:deepseek-ai/deepseek-v4-pro\",\"atlas:zai-org/glm-5.2\"]`.\n  One `models` entry can be the group token **`twin`** — the *twin flames* — which\n  expands to **both Anthropic reasoners at once, `fable` + `opus`**. Both ride the\n  same OAuth session as `ask`/`ask_opus5`, so `models=[\"twin\"]` is a dual\n  Fable/Opus 5 invocation that needs no provider keys at all — the cheapest real\n  second opinion available — and `models=[\"twin\",\"minimax\"]` adds a third voice to\n  it. `twins`, `twin flames`, `twin-flame` and `twin_flames` all name the same\n  pair. A group only makes sense where a *list* of models is taken; a\n  single-model slot (`synthesizer`, and the debate roles) rejects it with a\n  `bad_args` error rather than silently keeping just Fable.\n  Instead of listing `models`, pass a named\n  **`tier`**: `\"default\"` (fable+minimax, +deepseek when `ASK_FABLE_DEEPSEEK_API_KEY`\n  is set — cheap direct models are preferred and consulted first) ·\n  `\"twin\"` (the twin flames, fable+opus) ·\n  `\"middle\"` (+opus+glm+gemini+codex+grok+kimi, cheap-first order) · `\"full\"`\n  (+the configured Ollama Cloud models). An explicit `models` list overrides `tier`.\n  The result adds a **`consensus`** signal (`strong`/`partial`/`divergent`/`unknown`) +\n  `material_disagreement` computed from the panel's recommendations, each `sources`\n  entry shows that model's `recommendation`, and the synthesizer sees the panel\n  **anonymized** (Expert A/B, Fable last) so it can't favor its own answer — on a\n  material split it's told to pick a side, not average.\n- **`ask_chain(question, context=\"\", pipeline=\"m3 > glm > deepseek > fable\")`** — the\n  **sequential** counterpart to `ask_council`: thread a question through an **ordered**\n  pipeline (a `pipeline` string split on `>`, or an ordered `models` array), each stage\n  refining the last. Stage 1 **drafts**; each middle stage is told to solve\n  independently and **critique** the prior draft before extending it (an anti-anchoring\n  guard); the final stage **decides**, seeing all prior stages anonymized as peers. Order\n  matters and repeats are allowed (`fable > glm > fable` = draft → critique → re-decide;\n  alias `m3` = minimax). The `twin` group token expands **in place to two stages**,\n  `fable` then `opus` — so `m3 > twin` is a cheap draft finished by both Anthropic\n  reasoners in turn. A stage that refuses/errors is **skipped** (recorded) and the\n  chain continues; if the final stage fails, Fable synthesizes the survivors. The result\n  adds a **`recommendation_drift`** trail + **`material_drift`** flag — the chain analogue\n  of the council's consensus signal, so you can see whether the answer was refined or just\n  rubber-stamped. Best for **cost-tiered escalation** (a cheap/fast model drafts, Fable\n  finalizes) and explicit **draft → red-team → decide** pipelines; costs more latency than\n  a council (stages run in sequence, not parallel), so reserve it for when the ordered\n  refinement is the point.\n- **`ask_debate(question, context=\"\", proposer=\"fable\", opponent=\"minimax\", adjudicator=\"fable\", rounds=1)`** —\n  the **adversarial** counterpart: pit two models AGAINST each other, then have a fresh\n  anonymized third model adjudicate. The `proposer` commits to a position decomposed into\n  load-bearing **claims**; the `opponent` must **dispose of each claim** (concede, or\n  contest with a concrete failure scenario); the proposer **revises** under fire; the\n  `adjudicator` **rules** on the merits. Pick the pair (e.g. `opponent=\"codex\"` for\n  **Fable vs GPT-5.6 Sol**, or `opponent=\"glm\"`) and, if you want someone other than\n  Fable ruling, the judge (`adjudicator=\"opus\"` for Claude Opus 5) — keep it off the\n  debating pair so the ruling stays third-party. `rounds=2` adds a rebuttal pass. The\n  outcome is decided **server-side** from the ledger, surfaced as `debate.resolution`:\n  **`conceded`** (opponent conceded everything), **`converged`** (all contests resolved\n  and both sides agree), **`adjudicated`** (the adjudicator decided), or **`stalemate`**\n  (both dug in with nothing new → confidence is mechanically downgraded). Also returns\n  `recommendation_drift`, `decisive_argument`, and `low_effort_opposition`. Degrades to a\n  single-critic pass if the opponent is unconfigured. The **most expensive** mode (up to\n  four sequential calls), so reserve it for a genuinely contentious, hard-to-reverse\n  decision. Aliases: `m3` = minimax, `gpt` = codex, `opus5` = opus.\n- **`ask_ollama(question, context=\"\", model=...)`** — guarded reasoning from a\n  single **Ollama Cloud** model on its own. `model` is a cloud model id (e.g.\n  `kimi-k2.7-code:cloud`, `gpt-oss:120b-cloud`, `deepseek-v3.2:cloud`); omit it to\n  use `ASK_FABLE_OLLAMA_MODEL`. Single-turn. Reached via your local `ollama`\n  daemon by default (needs `ollama signin`; no API key) — point\n  `ASK_FABLE_OLLAMA_BASE_URL` at `https://ollama.com` (+ key) for direct cloud.\n- **`ask_ollama_council(question, context=\"\", models=[...])`** — fan a question\n  out to **several Ollama Cloud models** (an `ollama:` prefix on each id is\n  optional), then have Fable synthesize their answers into one — same contract as\n  `ask_council`, but the council is Ollama-only. Omit `models` to use the server's\n  configured set (the config file or `ASK_FABLE_OLLAMA_COUNCIL`, default:\n  `minimax-m3:cloud`, `glm-5.2:cloud`, `nemotron-3-ultra:cloud`,\n  `qwen3-coder:480b-cloud`, `kimi-k2.7-code:cloud`, `deepseek-v4-pro:cloud`,\n  `gpt-oss:120b-cloud` — kept lean; the 675b/397b generalists are left out so the\n  parallel council stays fast, add them per call if you want them).\n- **`ask_atlas_council(question, context=\"\", models=[...], synthesizer=...)`** —\n  the **Atlas-only council with GPT-5.6 Sol as the default adjudicator**. Fans the\n  question out to several Atlas Cloud models (an `atlas:` prefix on each id is\n  optional), then the adjudicator reconciles them: the **local `codex` CLI**\n  (GPT-5.6 Sol, no Atlas tokens) when installed → Atlas-hosted\n  `openai/gpt-5.6-sol` → Fable. Omit `models` to use the configured set\n  (`configure_atlas_council` / `ASK_FABLE_ATLAS_COUNCIL`), else **3 featured\n  catalog models** (one per provider). `xai/grok-*` members reroute to the local\n  `grok` CLI keylessly; anything else needs the Atlas API key. The result's\n  `synthesis` block reports which adjudicator actually ran (and any fallback).\n  The same `synthesizer` parameter also works on plain `ask_council`.\n\n### Setup and reusable context\n\n- **`list_ollama_models(refresh=true)`** — discover what's actually available for\n  the council: the **live `ollama.com` catalog** (GLM, MiniMax-M3, Qwen, Kimi,\n  DeepSeek, Nemotron, Mistral, gpt-oss, …) as daemon-ready ids, the models already\n  **pulled locally**, and the **currently-configured council**. Read-only.\n- **`configure_ollama_council(models=[...], default_model=...)`** — **save** a\n  chosen Ollama council so it sticks across sessions. Writes ask_fable's config\n  file (`${XDG_CONFIG_HOME:-~/.config}/ask_fable/config.json`), which **overrides**\n  the `ASK_FABLE_OLLAMA_*` env defaults. Bare names are normalized (`minimax-m3` →\n  `minimax-m3:cloud`); an `ollama:` prefix is optional. Together these two tools\n  let an agent, the first time you want an Ollama council, **offer to set it up** —\n  list the options, ask which you want, and persist your pick — instead of you\n  hand-editing env vars.\n- **`configure_atlas_council(models=[...], synthesizer=...)`** — **save** a chosen\n  Atlas council (and optionally its adjudicator) so it sticks across sessions.\n  Writes the same config file (`atlas_council` / `atlas_synthesizer` keys), which\n  **overrides** the `ASK_FABLE_ATLAS_COUNCIL` / `ASK_FABLE_ATLAS_SYNTHESIZER` env\n  defaults. An `atlas:` prefix is optional; aliases resolve (`gpt` persists as\n  `codex`, a bare `openai/gpt-5.6-sol` as `atlas:openai/gpt-5.6-sol`). Ground the\n  picks with `list_atlas_models` first.\n- **`configure_tracing(trace_mode=\"safe\"|\"full\", stream_reasoning=true|false)`** —\n  toggle reasoning-trace capture **at runtime**, persisted to the same config file.\n  `trace_mode=\"full\"` records redacted model reasoning into traces / trace bundles\n  (and saves answer markdown); `stream_reasoning` streams model thinking live to the\n  server console. Both **override** the `ASK_FABLE_TRACE_MODE` /\n  `ASK_FABLE_STREAM_REASONING` env defaults and apply on the next call — no\n  `~/.claude.json` edit or restart. Pass either or both.\n- **`context_write(key, value, description=\"\")`** — the **context bus**: store a\n  chunk of context (code, a stack trace, design notes) under a stable `key`, then\n  reference it via `context_ref` on any ask tool instead of re-pasting. Shared by\n  every agent on the server (a sibling agent can read it); reusing a key overwrites.\n  A durable best-effort SQLite store (`${XDG_STATE_HOME}/ask_fable/context.db`,\n  override with `ASK_FABLE_CONTEXT_PATH`).\n- **`context_read(key)`** / **`context_list()`** / **`context_delete(key)`** —\n  read back a stored blob (+ size/age/description), list what's stored (keys +\n  metadata, never the full values), or delete one. `context_list` is the way to\n  discover what's already available before re-pasting.\n- **`ask_fable_help(topic=\"all\")`** — the server's manual on demand: free, local,\n  instant, no model call. Claude Code (and other harnesses) truncate the MCP\n  standing-instructions field at ~2 KB, so that field carries only the triggers\n  and the rest lives here — what to do with a refusal, the shared context bus,\n  configuring Ollama / Atlas / OpenRouter councils, and the full tool menu with\n  every model token. Call it with no argument for everything; every response\n  lists the topics it accepts. Guard refusals also carry a `how_to_reframe`\n  field pointing back at it.\n- **`reset_session(session=\"default\", model=\"fable\", save=true)`** — dump the transcript\n  (each turn's Q/A and any provider reasoning captured for that turn) to\n  `${XDG_STATE_HOME}/ask_fable/sessions/<key>-<ts>.md` (when `save`) and clear it.\n  `model` selects which tool's conversation to clear — `\"fable\"` for `ask`, `\"opus5\"`\n  for `ask_opus5` (they namespace sessions separately).\n\n### Operations and observability\n\n- **`stats(window=\"24h\", by=\"model\", model=..., session=...)`** — read-only\n  usage/health stats aggregated from the audit log (rotations included): per-bucket\n  calls / allowed / refused / errors, avg + p95 latency, and error rate, plus totals.\n  `window` is `1h`/`24h`/`7d`/`all`; `by` buckets per `model` (a council counts under\n  its synthesizer), `provider` (per backend call — the only view that sees\n  council/chain/debate members one by one), `tool`, `session`, `day`, `project`,\n  `cache`, or `mode`; the optional filters narrow to one backend or workflow. Calls\n  the circuit breaker shed are reported as `circuit_open`, not as errors or\n  latency. Council/chain audit records\n  also carry `quorum`/`consensus`/`synth_fallback`, so you can see degradation trends\n  (\"have my councils been running 1-of-5 all day?\") — no model call, never cached.\n- **`trace_list(limit=20, tool=..., status=..., provider=..., session=..., project=..., before=...)`** —\n  list recent schema-v2 request traces without raw content. Filter by request or\n  provider metadata and page with `before`.\n- **`trace_get(trace_id, include_content=false, max_chars=4000)`** — read one ordered\n  event timeline and its artifact references. In full mode, `include_content=true`\n  returns a redacted, bounded excerpt of that trace's bundle.\n\n### Cross-instance session hub\n\nThe hub is a **local coordination dashboard**, not a second form of model memory.\nEvery successful ask that passes the tool-level cache is mirrored *after* its\noracle result is available. That result can still come from an underlying\nper-oracle cache.\nUse a meaningful shared `session` label when several local agents are working the\nsame decision, then inspect that work without re-running it:\n\n```jsonc\nask({ \"question\": \"Which migration path is safest?\", \"context\": \"…\", \"session\": \"db-migration\" })\n\nsession_list({})                              // live sessions in this project\nsession_peek({ \"session_key\": \"db-migration\" }) // retained Q/A turns across agents\nsession_stats({ \"window_s\": 86400 })          // 24-hour totals by oracle, agent, status\n```\n\n- **`session_list(all_projects=false, active_only=true, limit=50)`** lists one\n  row per `(session_key, agent_id)`, newest first. It is scoped to the current\n  project by default; `all_projects:true` exposes every project recorded by this\n  local database. `active_only:true` hides sessions whose heartbeat is older than\n  five minutes (tune with `ASK_FABLE_HUB_STALE_SECONDS`); pass `false` to include\n  retained history. `limit` is 1–200.\n- **`session_peek(session_key, agent_id=None)`** returns the full retained question\n  and answer history in chronological order. It intentionally spans projects for\n  a matching session label; pass `agent_id` to narrow it. Choose labels that do\n  not collide across sensitive work, and do not use this tool where you are not\n  permitted to read the local user’s other project data.\n- **`session_stats(all_projects=false, window_s=86400)`** aggregates turns by\n  oracle, agent, and status across MCP instances. It is project-scoped by default;\n  `window_s:0` includes all retained turn history. Its session totals are not\n  restricted to that time window.\n\nThe hub is deliberately **visibility-only**: it is never read by `ask`, councils,\nchains, or debates; it cannot resume Fable's per-process `SessionStore`; and\nrefused/error turns are not mirrored. It therefore cannot feed another agent’s\nhistory back into an oracle answer automatically. An agent can still explicitly\nread a hub turn and relay it in a later prompt. It retains complete questions and\nanswers, plus session, agent, project, oracle, status, timing, and an SDK session\nidentifier when supplied. It does not separately store the supplied `context`,\nalthough a response can echo it. Treat its database as sensitive. The default path is\n`${XDG_STATE_HOME:-~/.local/state}/ask_fable/hub.db` (new files are owner-only\n`0600`, SQLite WAL, per-operation connections). It is machine-local unless you\ndeliberately set `ASK_FABLE_HUB_PATH` to shared storage.\n\nFor `ask_council`, `ask_chain`, `ask_debate`, `ask_ollama_council`, and\n`ask_atlas_council`, `session` is a hub coordination key, not a Fable multi-turn\nsession; if omitted it defaults to the tool name. Hub retention is a best-effort row cap, not a deletion schedule:\nthe default 10,000 stored turns are swept oldest-first roughly every 100 writes.\nDisabling the hub stops future reads and writes but does not delete already stored\ndata.\n\n</details>\n\n### Configure the Ollama council\n\nYou never have to hand-edit env vars to choose your Ollama council — the agent can\nset it up for you. The **first time** you want an Ollama council (or any time you\nsay *\"configure ask_fable\"* / *\"set up the council\"*), the server's instructions\nprompt the agent to:\n\n1. call **`list_ollama_models`** — which returns the live `ollama.com` catalog, the\n   models already pulled locally, and the currently-configured council:\n\n   ```json\n   { \"status\": \"ok\", \"reachable\": true,\n     \"available_cloud\": [\"deepseek-v4-pro:cloud\", \"glm-5.2:cloud\", \"minimax-m3:cloud\",\n                          \"mistral-large-3:675b-cloud\", \"nemotron-3-ultra:cloud\",\n                          \"qwen3-coder:480b-cloud\", \"...\"],\n     \"pulled_local\": [\"gpt-oss:120b-cloud\"],\n     \"configured_council\": [\"minimax-m3:cloud\", \"glm-5.2:cloud\", \"...\"],\n     \"config_file\": \"~/.config/ask_fable/config.json\" }\n   ```\n\n2. **ask you** which of those you want, then call **`configure_ollama_council`** with:\n\n   ```json\n   {\n     \"models\": [\n       \"minimax-m3\",\n       \"glm-5.2\",\n       \"qwen3-coder:480b-cloud\",\n       \"deepseek-v4-pro\"\n     ],\n     \"default_model\": \"gpt-oss:120b-cloud\"\n   }\n   ```\n\nThe choice is written to `${XDG_CONFIG_HOME:-~/.config}/ask_fable/config.json`\n(`{\"ollama_council\": [...], \"ollama_model\": \"...\"}`) and **overrides** the\n`ASK_FABLE_OLLAMA_COUNCIL` / `ASK_FABLE_OLLAMA_MODEL` env vars — so it persists\nacross sessions and every later `ask_ollama_council` (and the `full` tier) uses it.\nPrecedence, highest first: **config file → env var → built-in default**.\n\n## Observability and response shape\n\nEvery MCP call receives a correlated `trace_id`. **Safe mode** (the default) writes\nschema-v2 metadata only: no raw prompt or answer is included in that trace. The separate\nlegacy audit log can store raw values only when its explicit `ASK_FABLE_AUDIT_RAW` switch\nis enabled. **Full mode** writes a redacted, size-capped trace bundle under\n`${XDG_STATE_HOME}/ask_fable/traces/`; it may include provider-emitted reasoning and tool\nactivity when available. `trace_list` finds recent calls and `trace_get` reads a timeline\nor a bounded bundle excerpt.\n\nAnswer Markdown is separate: `ASK_FABLE_SAVE=1` saves every successful answer under\n`${XDG_STATE_HOME}/ask_fable/answers/` (override with `ASK_FABLE_OUTPUT_DIR`),\n`ASK_FABLE_SAVE=0` disables it, and an unset setting saves only in full trace mode.\nIts path is returned as `\"saved\"`. Saved files include a `## Thinking` section only\nwhen a provider emitted reasoning. This is independent of `reset_session` dumps.\n\n### Response contract\n\nEvery tool returns one JSON object. A successful answer looks like this:\n\n```json\n{\n  \"status\": \"ok\",\n  \"answer\": \"…\",\n  \"sidecar\": {\n    \"recommendation\": \"apply\",\n    \"confidence\": \"high\",\n    \"needs_context\": []\n  },\n  \"trace_id\": \"…\",\n  \"telemetry\": { \"status\": \"ok\" }\n}\n```\n\n`sidecar` is `{recommendation, confidence, needs_context}` (null when the model emitted\nno parseable one). When the model wants more it also carries\n`\"followup\":{\"needs_context\":[...],\"how\":...,\"likely_already_pasted\":[...]}`, and a\nstuck re-ask loop terminates with `\"status\":\"context_exhausted\"` (+ best-effort answer;\ntune the cap with `ASK_FABLE_MAX_NEEDS_CONTEXT`, default 2). Any `context_ref` keys used\nare echoed as `\"context_ref_resolved\":[...]` / `\"context_ref_missing\":[...]`; an\nall-missing ref with no other context returns `\"status\":\"needs_context\"` (+ a\n`did_you_mean` suggestion) without calling the model.\n\n**Councils** add `\"mode\":\"council\"`, `\"synthesizer\"`, `\"sources\"` (each entry with that\nmodel's `recommendation`), the `\"consensus\"`/`\"material_disagreement\"` signal, plus a\nsmall **envelope** so you can tell whether the council degraded: `\"quorum\":\"N/M\"`\n(answered / asked), `\"effective_models\":[...]`, `\"degraded\":bool`,\n`\"confidence\":\"high|medium|low\"`, and `\"recommended_next_action\":...` — a `1/M` quorum is\none opinion, not consensus.\n\n**Chains** (`ask_chain`) add `\"mode\":\"chain\"`, the `\"pipeline\"` (ordered model labels),\n`\"answered_by\"`, a lean `\"stages\"` list (each with `stage`/`model`/`role`/`status`/\n`recommendation`/`confidence`), the `\"recommendation_drift\"` trail + `\"material_drift\"`\nflag, and `\"answered\":N`/`\"requested\":M`; a `\"fallback\"` note appears when a failed final\nstage was reconciled by Fable.\n\n**Debates** (`ask_debate`) add `\"mode\":\"debate\"`, `\"answered_by\"`, a lean `\"turns\"` list\n(each with `role`/`model`/`status`/`recommendation`/`confidence`), and a `\"debate\"` block:\n`\"pairing\"`, `\"rounds\"`, `\"resolution\"` (`conceded`/`converged`/`adjudicated`/`stalemate`/\n`degraded_single_critic`), `\"contested_claims_remaining\"`, `\"recommendation_drift\"`,\n`\"low_effort_opposition\"`, `\"material_disagreement\"`, and `\"decisive_argument\"` (the\nadjudicator's quoted pivot). The full transcript goes to the saved markdown file, not the\ninline reply. Shares the `ASK_FABLE_CHAIN_TIMEOUT` wall-clock bound.\n\n**Failure responses** are structured too:\n\n```json\n{ \"status\": \"refused\", \"stage\": \"guard\", \"reason\": \"…\" }\n```\n\n```json\n{ \"status\": \"error\", \"kind\": \"timeout\", \"detail\": \"…\" }\n```\n\n### Caching\n\nThe single-shot tools (`ask_m3`/`ask_glm`/`ask_deepseek`/`ask_gemini`/`ask_codex`/`ask_grok`/`ask_kimi`/`ask_ollama`/`ask_atlas`/`ask_openrouter`), the councils, and\n`ask_chain` (keyed on the **ordered** pipeline) **cache**\nsuccessful answers keyed on `hash(tool + models + normalized question + context)`.\nAn exact re-ask within the freshness window returns instantly with `\"cached\":true`,\n`\"cache_age_s\":N`, and a duplicate-nudge `\"note\"` — so a local agent's edit/verify\nre-ask loop doesn't pay for the model every time. `ask` (multi-turn) is never cached.\nTune with `ASK_FABLE_CACHE_TTL` (seconds, default 3600) or disable with\n`ASK_FABLE_CACHE=0`.\n\n### Console progress\n\nAll ask tools print a tidy, TTY-colored trace of what's happening — guard\nresult, each model being asked, elapsed time, reasoning excerpts, and the\nsynthesis step — to **stderr** (Claude Code surfaces this in its MCP logs /\n`claude --debug`; in\na terminal it prints live). stdout is reserved for the JSON-RPC protocol. Silence\nit with `ASK_FABLE_QUIET=1`; hide just the model reasoning with\n`ASK_FABLE_SHOW_REASONING=0`. Stream Fable's reasoning **live** (block by block, as it\narrives) with `ASK_FABLE_STREAM_REASONING=1` instead of one post-hoc excerpt — Fable\nonly, since the other backends don't stream. To surface a reasoning excerpt **inline in\nthe tool result** (so it shows in the Claude Code conversation, not just the stderr\ntrace), set `ASK_FABLE_RETURN_THINKING=1`, capped by `ASK_FABLE_THINKING_CHARS` (default\n4000). Full trace bundles are written only in full trace mode; answer Markdown follows\nthe `ASK_FABLE_SAVE` policy described above.\n\n### Backend setup\n\nFor `ask_council`'s **MiniMax** oracle, install the MiniMax `mmx` CLI and log in\nonce (`mmx auth login`) — the server sets no key, it reuses that session exactly\nas the Fable bridge reuses Claude Code's OAuth. `ask-fable` always passes\n`--model MiniMax-M3` explicitly, but the `mmx` CLI's own default is older\n(`MiniMax-M2.7`); standardize it once with\n`mmx config set --key default_text_model --value MiniMax-M3` so ad-hoc `mmx`\ncalls match. The **Gemini** oracle works the same way: install the `agy` CLI and\nsign in once — the server sets no key and reuses that session. `ask-fable` calls it\nin non-interactive print mode (`agy --model \"Gemini 3.1 Pro (High)\" -p \"<prompt>\"`)\nand reads the plain-text answer from stdout. Pick a different `agy` model (run\n`agy models` to list them) with `ASK_FABLE_GEMINI_MODEL`. The **Codex** oracle\nworks the same way: install OpenAI's `codex` CLI and run `codex login` once — the\nserver sets no key and reuses that session. `ask-fable` calls it non-interactively\n(`codex exec`) with a **hermetic, read-only** invocation (`--ignore-user-config`\n`--sandbox read-only`), so the operator's own `~/.codex/config.toml` and hooks\ncan't change the answer and it can't touch the repo. Pick a different model with\n`ASK_FABLE_CODEX_MODEL` and its reasoning effort with `ASK_FABLE_CODEX_REASONING`\n(default `high`). The **Ollama Cloud**\noracles work\nthe same way: install `ollama`, run `ollama signin` once, and the local daemon\nproxies `:cloud` models — **no API key needed** (this is the default;\n`ASK_FABLE_OLLAMA_BASE_URL=http://localhost:11434`). To hit `ollama.com` directly\ninstead, set `ASK_FABLE_OLLAMA_BASE_URL=https://ollama.com` and an\n`ASK_FABLE_OLLAMA_API_KEY`. The **GLM** and **DeepSeek** oracles are\nAnthropic-Messages-compatible HTTP endpoints, while Atlas uses the OpenAI chat\nshape; enable them by putting their keys in the server's registration `env` (in\n`~/.claude.json`, kept out of the repo), e.g.:\n\n```json\n{ \"mcpServers\": { \"ask_fable\": { \"command\": \"ask-fable\", \"env\": {\n  \"ASK_FABLE_GLM_API_KEY\": \"<z.ai key>\",\n  \"ASK_FABLE_DEEPSEEK_API_KEY\": \"<deepseek key>\",\n  \"ASK_FABLE_ATLAS_API_KEY\": \"<Atlas Cloud key>\"\n} } } }\n```\n\n## Configuration reference\n\nMost installations only need a registered Fable bridge. Configure an optional\nbackend, persistence, or trace limit only when you need it; the full reference is\ngrouped below for operators.\n\n| Var | Default | Meaning |\n|---|---|---|\n| `ASK_FABLE_MIN_LEN` / `ASK_FABLE_MAX_LEN` | 3 / 65536 | question length bounds |\n| `ASK_FABLE_MAX_CONTEXT_LEN` | off (unbounded) | optional context cap; any value is floored to **512,000** chars |\n| `ASK_FABLE_TIMEOUT` | 240 | per-turn wall-clock seconds |\n| `ASK_FABLE_MAX_NEEDS_CONTEXT` | 2 | consecutive `needs_more_context` turns on a session before `ask` returns `context_exhausted` (0 = stop after the first) |\n| `ASK_FABLE_USE_CLI` | off | force the `claude` CLI bridge instead of the SDK |\n| `ASK_FABLE_FABLE_MODEL` | unset (ladder) | pin the exact Fable id for `ask` and the `fable` oracle, skipping the newest-first ladder (`claude-fable-5-1` → `claude-fable-5`). A pinned call never falls back — if that id can't run, the turn fails and says so |\n| `ASK_FABLE_CLAUDE_CLI` | unset (auto) | pin the Claude Code binary the Agent SDK spawns. By default the SDK prefers the copy vendored inside `claude-agent-sdk`, which can be months behind the one on your PATH and too old for a newly released model; the bridge hands it the PATH binary instead when that one is strictly newer |\n| `ASK_FABLE_MINIMAX_MODEL` | `MiniMax-M3` | model id for the `ask_council` MiniMax oracle |\n| `ASK_FABLE_GEMINI_MODEL` | `Gemini 3.1 Pro (High)` | `agy` model name for the `ask_gemini` tool / `gemini` council oracle (run `agy models` to list; via the `agy` CLI) |\n| `ASK_FABLE_GEMINI_TIMEOUT` | falls back to `ASK_FABLE_TIMEOUT`, else 240 | per-turn seconds for the `agy`/Gemini oracle specifically — cap this agentic CLI without lowering the global timeout. On timeout the whole `agy` process group is SIGKILLed (it spawns children), so a slow turn can't hang the call or leak orphans |\n| `ASK_FABLE_CODEX_MODEL` | `gpt-5.6-sol` | model id for the `ask_codex` tool / `codex` council oracle (via the `codex` CLI) |\n| `ASK_FABLE_CODEX_REASONING` | `high` | reasoning effort passed to `codex exec` (`model_reasoning_effort`) |\n| `ASK_FABLE_CODEX_TIMEOUT` | falls back to `ASK_FABLE_TIMEOUT`, else 240 | per-turn seconds for the `codex` oracle specifically. On timeout the whole `codex` process group is SIGKILLed (it spawns children), so a slow turn can't hang the call or leak orphans |\n| `ASK_FABLE_GROK_MODEL` / `ASK_FABLE_GROK_REASONING` / `ASK_FABLE_GROK_TIMEOUT` | `grok-4.6` / `low` / falls back to `ASK_FABLE_TIMEOUT` | local `grok` CLI settings. `quick`, `standard`, and `deep` effort presets map to low reasoning to keep context-heavy turns bounded; set `ASK_FABLE_GROK_REASONING` explicitly for Grok-native medium/high |\n| `ASK_FABLE_KIMI_MODEL` / `ASK_FABLE_KIMI_EFFORT` / `ASK_FABLE_KIMI_TIMEOUT` / `ASK_FABLE_KIMI_HOME` | `kimi-code/k3` / `high` / falls back to `ASK_FABLE_TIMEOUT` / `~/.kimi-code` | local `kimi` CLI settings. Effort accepts `low`/`high`/`max` plus the `quick`/`standard`/`deep` presets. The prompt travels as one argv value, so prompts above ~120k bytes are refused with `context_too_large` — use `ask_atlas` with `moonshotai/kimi-k3` for bigger context |\n| `ASK_FABLE_CLI_MAX_PARALLEL` | 2 | maximum concurrent local CLI processes **per binary** (`claude`, `mmx`, `grok`, `codex`, `agy`, `kimi`); `0` or negative disables this gate and `1` serializes each CLI family. The queue wait counts against the call's own timeout, so a call parked behind busy slots fails as a timeout instead of waiting unboundedly |\n| `ASK_FABLE_GLM_API_KEY` | — | Z.ai key that enables the `glm` council oracle (unset = oracle unavailable) |\n| `ASK_FABLE_GLM_BASE_URL` / `_MODEL` | `https://api.z.ai/api/anthropic` / `glm-5.2` | GLM endpoint + model |\n| `ASK_FABLE_DEEPSEEK_API_KEY` | — | DeepSeek key that enables the `deepseek` council oracle |\n| `ASK_FABLE_DEEPSEEK_BASE_URL` / `_MODEL` | `https://api.deepseek.com/anthropic` / `deepseek-v4-pro` | DeepSeek endpoint + model |\n| `ASK_FABLE_ATLAS_API_KEY` / `ATLASCLOUD_API_KEY` | — | Atlas Cloud key for HTTP `ask_atlas` and `atlas:<model-id>` calls (either name is accepted); local `xai/grok-*` routes reuse the authenticated `grok` CLI |\n| `ASK_FABLE_ATLAS_BASE_URL` | `https://api.atlascloud.ai` | Atlas Cloud API base URL; override only with a trusted compatible endpoint because it receives the bearer key and request content |\n| `ASK_FABLE_ATLAS_MODEL` | `xai/grok-4.6` | default model for `ask_atlas` when no model is passed |\n| `ASK_FABLE_OPENROUTER_API_KEY` / `OPENROUTER_API_KEY` | — | OpenRouter key for `ask_openrouter`, `ask_openrouter_council` and `openrouter:<model-id>` tokens (either name is accepted; the catalog needs no key) |\n| `ASK_FABLE_OPENROUTER_MODEL` | `deepseek/deepseek-v4-pro` | default model for `ask_openrouter` when none is passed |\n| `ASK_FABLE_OPENROUTER_COUNCIL` / `_SYNTHESIZER` / `_EFFORT` | — | default panel, adjudicator and effort for `ask_openrouter_council` (config keys `openrouter_council` / `openrouter_synthesizer` / `openrouter_effort` win) |\n| `ASK_FABLE_ATLAS_EFFORT` / `ASK_FABLE_EFFORT` | `deep` | default Atlas effort (`quick`, `standard`, or `deep`); `atlas_effort` / `effort` in the config file override environment values |\n| `ASK_FABLE_ATLAS_COUNCIL` | — | default members for `ask_atlas_council` (comma/space list of Atlas model ids; config file `atlas_council` overrides); unset → 3 featured catalog models |\n| `ASK_FABLE_ATLAS_SYNTHESIZER` | — | adjudicator for `ask_atlas_council` (any council token; config file `atlas_synthesizer` overrides); unset → local `codex` CLI → `atlas:openai/gpt-5.6-sol` → `fable` |\n| `ASK_FABLE_OLLAMA_API_KEY` | — | only for a **remote** endpoint (`ollama.com`); the default local daemon needs no key |\n| `ASK_FABLE_OLLAMA_BASE_URL` | `http://localhost:11434` | Ollama endpoint (POSTs `/api/chat`). Default is the local daemon, which proxies `:cloud` models via `ollama signin`; set `https://ollama.com` (+ key) for direct cloud |\n| `ASK_FABLE_OLLAMA_MODEL` | `gpt-oss:120b-cloud` | default model for `ask_ollama` when none is passed (config file `ollama_model` overrides) |\n| `ASK_FABLE_OLLAMA_COUNCIL` | `minimax-m3:cloud, glm-5.2:cloud, nemotron-3-ultra:cloud, qwen3-coder:480b-cloud, kimi-k2.7-code:cloud, deepseek-v4-pro:cloud, gpt-oss:120b-cloud` | models for the `full` tier + default `ask_ollama_council` (comma/space list; config file `ollama_council` overrides) |\n| `ASK_FABLE_CONFIG_FILE` | `${XDG_CONFIG_HOME:-~/.config}/ask_fable/config.json` | tool-writable config (`ollama_council`, `ollama_model` via `configure_ollama_council`; `atlas_council`, `atlas_synthesizer` via `configure_atlas_council`; `ASK_FABLE_TRACE_MODE`, `ASK_FABLE_STREAM_REASONING` via `configure_tracing`); overrides the matching env vars |\n| `ASK_FABLE_OLLAMA_CATALOG_URL` | `https://ollama.com` | where `list_ollama_models` fetches the cloud catalog (`/api/tags`) |\n| `ASK_FABLE_MAX_TOKENS` | 65536 | max output tokens for GLM/DeepSeek, Ollama (`num_predict`), and MiniMax (`--max-tokens`); Fable uses the model default |\n| `ASK_FABLE_QUIET` | off | silence the stderr progress/reasoning trace |\n| `ASK_FABLE_SHOW_REASONING` | on | show model reasoning excerpts in the trace |\n| `ASK_FABLE_STREAM_REASONING` | off | live-stream Fable's reasoning block-by-block to the stderr trace as it arrives (Fable only; other backends don't stream) |\n| `ASK_FABLE_RETURN_THINKING` | off | attach a capped reasoning excerpt (`thinking`) to the tool result body so it renders inline in the client |\n| `ASK_FABLE_THINKING_CHARS` | 4000 | cap for the `ASK_FABLE_RETURN_THINKING` excerpt |\n| `ASK_FABLE_DENYLIST_FILE` | — | extra denylist terms (one per line) for the fallback |\n| `ASK_FABLE_ALLOWLIST_FILE` | — | benign phrases (one per line) neutralized before matching, to rescue false positives like `request payload`; rescues only the exact phrase |\n| `ASK_FABLE_PROJECT_ROOT` | — | project root that `context_pack` may read from; **unset disables `context_pack`** (returns `not_configured`). Reads never escape this root |\n| `ASK_FABLE_PACK_MAX_CHARS` | 24000 | default total-character budget for a `context_pack` bundle (over-budget specs are reported in `skipped`, never truncated) |\n| `ASK_FABLE_PACK_MAX_FILES` | 32 | max files admitted in one `context_pack` |\n| `ASK_FABLE_PACK_MAX_FILE_BYTES` | 1000000 | per-file read cap for `context_pack` (a whole file over this is skipped `too_large`; a line-range is capped on bytes collected) |\n| `ASK_FABLE_AUDIT_PATH` | `$XDG_STATE_HOME/ask_fable/decisions.jsonl` | audit log |\n| `ASK_FABLE_AUDIT_RAW` | off | store raw questions (and raw context unless overridden); otherwise store SHA-256 metadata only |\n| `ASK_FABLE_AUDIT_RAW_CONTEXT` | follows `ASK_FABLE_AUDIT_RAW` | split switch for `context_raw` only — set `0` with `AUDIT_RAW=1` to keep raw questions for debugging while context (the larger proprietary-code / secret-bearing surface) stays hashed-only |\n| `ASK_FABLE_CACHE` | on | cache successful single-shot/council answers to spare re-ask loops; set `0` to disable |\n| `ASK_FABLE_CACHE_TTL` | 3600 | cache freshness window in seconds |\n| `ASK_FABLE_CACHE_PATH` | `$XDG_STATE_HOME/ask_fable/cache.db` | SQLite cache location |\n| `ASK_FABLE_CACHE_MAX_ROWS` | 10000 | row cap for the answer cache — a periodic sweep (every ~100 writes) deletes TTL-expired rows and trims to 90% of the cap, oldest first |\n| `ASK_FABLE_CIRCUIT_BREAKER` | on | per-oracle circuit breaker: a chronically-failing backend is auto-skipped in council/chain fan-out (reported as `circuit_open` in `sources`, like `not_configured`); cache hits are still served. Never trips on `refused` or on config states (`not_configured`). Set `0` to disable |\n| `ASK_FABLE_BREAKER_WINDOW` | 20 | last N outcomes tracked per oracle |\n| `ASK_FABLE_BREAKER_THRESHOLD` | 0.5 | error rate over the window that opens the breaker (min 5 samples) |\n| `ASK_FABLE_BREAKER_COOLDOWN` | 300 | seconds an open breaker waits before allowing a half-open probe; a probe success closes it and clears the window |\n| `ASK_FABLE_CONTEXT_PATH` | `$XDG_STATE_HOME/ask_fable/context.db` | SQLite store for the context bus (`context_write`/`context_ref`) |\n| `ASK_FABLE_HUB` | on | set `0`, `false`, `no`, or `off` to disable the cross-instance session hub entirely |\n| `ASK_FABLE_HUB_PATH` | `$XDG_STATE_HOME/ask_fable/hub.db` | local SQLite hub database; point it at shared storage only when every reader is trusted |\n| `ASK_FABLE_HUB_MAX_ROWS` | 10000 | total retained hub-turn cap; a periodic oldest-first sweep trims history toward 90% of the cap |\n| `ASK_FABLE_HUB_STALE_SECONDS` | 300 | heartbeat age after which `session_list` considers a session stale |\n| `ASK_FABLE_HUB_PREVIEW_CHARS` | 160 | maximum `last_question` preview length returned by `session_list` |\n| `ASK_FABLE_AGENT_ID` | inferred from the MCP client | explicit hub attribution label; use it to distinguish local windows/agents when client metadata is not unique |\n| `ASK_FABLE_TRACE_MODE` | `safe` | `safe` stores correlated metadata only; `full` additionally stores redacted, size-capped trace bundles and answer Markdown |\n| `ASK_FABLE_TRACE_DIR` | `$XDG_STATE_HOME/ask_fable/traces` | directory for full-mode trace bundles |\n| `ASK_FABLE_TRACE_MAX_CONTENT_BYTES` | 104857600 (100 MiB) | maximum captured content per full trace bundle; truncation is recorded |\n| `ASK_FABLE_TRACE_MAX_EVENT_BYTES` | 1048576 (1 MiB) | maximum JSONL event-line size accepted while reading traces; oversized lines are discarded safely |\n| `ASK_FABLE_TRACE_QUERY_MAX_EVENTS` / `ASK_FABLE_TRACE_QUERY_MAX_BYTES` | 100000 / 52428800 (50 MiB) | upper bounds for one `trace_list` or `trace_get` scan |\n| `ASK_FABLE_PROJECT_ID` | derived from the working directory | stable project label stored with each trace; set explicitly to correlate calls across working directories |\n| `ASK_FABLE_SAVE` | unset | explicit `1` persists answer Markdown and explicit `0` disables it; when unset, Markdown is written only in full trace mode |\n| `ASK_FABLE_OUTPUT_DIR` | `$XDG_STATE_HOME/ask_fable/answers` | where saved answers are written (0600 files, 0700 dir) |\n| `ASK_FABLE_MAX_ANSWERS` | 0 (unlimited) | retention cap on saved answer Markdown files; only files ask-fable itself wrote (its own filename shape) are ever pruned. **The default answers dir is shared per user** — a cap set by one agent prunes the shared archive for all agents/projects |\n| `ASK_FABLE_MAX_SESSIONS` | 0 (unlimited) | retention cap on session transcript dumps; same ownership filter and shared-dir caveat as `ASK_FABLE_MAX_ANSWERS` |\n| `ASK_FABLE_COUNCIL_TIMEOUT` | `ASK_FABLE_TIMEOUT + 120` | hard upper bound (sec) on `ask_council` wall time — bounds the worst case where a backend swallows its own inner timeout. Oracles that already answered are preserved and synthesized; still-running ones are cancelled and shown as `kind:\"timeout\"` in `sources`. Only an all-timeout council surfaces `status:\"error\", kind:\"timeout\"` |\n| `ASK_FABLE_CHAIN_TIMEOUT` | `max(600, n × ASK_FABLE_TIMEOUT)` (min 10) | hard upper bound (sec) on `ask_chain` wall time (the chain is sequential, so the default scales with the number of stages). Surfaces as `status:\"error\", kind:\"timeout\"` with the partial `stages[]` collected so far |\n| `ASK_FABLE_MAX_PARALLEL` | 6 | semaphore size for council fan-out — bounds simultaneous sockets on the `full` tier so a 12-model fan-out can't exhaust `ulimit -n` |\n| `ASK_FABLE_AUDIT_MAX_BYTES` | 52428800 (50 MB) | size cap for the audit log; rotated to `decisions.<timestamp>.<seq>.jsonl` when exceeded |\n| `ASK_FABLE_AUDIT_BACKUPS` | unlimited | optional cap on rotated audit segments; set `0` to discard the active segment on rotation |\n\nDefault-created persisted state (cache, context bus, hub, audit log, saved answers,\nsession dumps, and full-trace bundles) is written to a per-user state dir, with\nnewly created files mode `0600` and parent dirs mode `0700`. SQLite stores\n(`cache.db`, `context.db`, `hub.db`) use WAL journal mode for crash safety. Markdown dumps (saved answers,\nsession transcripts) and the separately located config file go through an atomic\n`tempfile + os.replace + fsync` so a crash mid-write can never leave a\npartial or empty file on disk.\n\n## Recommended agent instructions\n\nThe server injects a short standing instruction so agents reach for these tools\nunprompted. But weak local models under-attend to system prompts, so for the best\nresults **also drop a decision ladder into your project's `CLAUDE.md` /\n`opencode.md`** (agents re-read those). Copy this block:\n\n```markdown\n## Using ask_fable (external reasoning)\n\n<p align=\"center\">\n  <img src=\"images/ai_council_new.jpg\" alt=\"Abstract representation of multiple A",
  "bytes": 60000,
  "sha": "82b75382588120cf55afad52b5869e175d8d94de501b791b1c4d9cd5ed1833b7",
  "repo_slug": "baggybin/ask-fable",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_baggybin_ask_fable_efa978c4/readme"
}