{"repo": "anthony-chaudhary/fak", "files": {"tools/accounts_policy.example.json": {"13f5f1a6e0391eb28bcbea759890fb009251e5d1": {"t": 1785674171.7945592, "n": 3581, "h": "843e79d82642a5b1", "c": "{\n \"_doc\": \"Account policy for the fleet resume/switcher layer (tools/fleet_accounts.py), applying uniformly to BOTH product families -- Claude (config dirs under the user home, e.g. ~/.claude*) and opencode (config dirs under XDG config home, e.g. ~/.config/opencode*). Copy this to tools/_registry/accounts_policy.json and edit; the live file under _registry/ is gitignored so each host keeps its own roster. 'exclude' substrings are matched against the bare account tag (e.g. 'backup', 'worker-a') and the dir basename; a match tombstones that account so it is never offered as a resume target. 'include_only' (when non-empty) is an allowlist: only listed tags are workers. 'account_profiles' can override model_tier/model/effort by account basename, product:tag, tag, or product. With NO live file present, fleet_accounts.py falls back first to this example, then to its built-in defaults (which already exclude backup/breakglass accounts, infer Claude as tier 1 Opus/xhigh, GLM-5.2 opencode as tier 2, unknowns as tier 3, and recognize the ranked NVIDIA NIM coding seats opencode-nim-deepseek-v4-pro, opencode-nim-kimi-k26, and opencode-nim-glm52 as tier 1 opencode seats).\",\n \"exclude\": [\"backup\", \"breakglass\"],\n \"include_only\": [],\n \"notes\": {\n \"backup\": \"break-glass backup account; never auto-resume\"\n },\n \"account_profiles\": {},\n \"_account_profiles_example\": {\n \"claude\": {\n \"model_tier\": 1,\n \"model\": \"opus\",\n \"effort\": \"xhigh\"\n },\n \"opencode:zai2\": {\n \"model_tier\": 2,\n \"model\": \"zai-coding-plan/glm-5.2\"\n },\n \"opencode:nim-deepseek-v4-pro\": {\n \"model_tier\": 1,\n \"model\": \"deepseek-ai/deepseek-v4-pro\",\n \"agent\": \"opencode\"\n },\n \"opencode:nim-kimi-k26\": {\n \"model_tier\": 1,\n \"model\": \"moonshotai/kimi-k2.6\",\n \"agent\": \"opencode\"\n },\n \"opencode:nim-glm52\": {\n \"model_tier\": 1,\n \"model\": \"z-ai/glm-5.2\",\n \"agent\": \"opencode\"\n }\n },\n \"route_weights\": {},\n \"_route_weights_example\": {\n \"gem7\": 10,\n \"gem8\": -10,\n \"opencode:nim-deepseek-v4-pro\": 30,\n \"opencode:nim-kimi-k26\": 20,\n \"opencode:nim-glm52\": 10\n },\n \"_nvidia_nim_coding_seats\": {\n \"ranked_default_order\": [\n \"opencode-nim-deepseek-v4-pro\",\n \"opencode-nim-kimi-k26\",\n \"opencode-nim-glm52\"\n ],\n \"base_url\": \"https://integrate.api.nvidia.com/v1\",\n \"api_key_env\": \"NVIDIA_API_KEY\",\n \"caveat\": \"moonshotai/kimi-k2.6 is ranked by current coding/agentic benchmark strength but NVIDIA marks the API unsupported after 2026-07-07; set opencode:nim-kimi-k26 to weight 0 or exclude that seat if it is no longer available.\"\n },\n \"_route_weights_doc\": \"route_weights maps an account (basename, product:tag, tag, or product) to an int capacity bias (default 0) for the switcher's tie-break: HIGHER = more room -> preferred EARLIER. The fleet has NO remaining-quota telemetry (an account probe only returns OK/AUTH/LIMIT, never headroom), so the router cannot itself tell which available account has the most quota left -- it otherwise balances purely on live/active SESSION counts. Use route_weights to encode out-of-band knowledge: lift an account you know has room (gem7:+10 above) and/or push a near-capped one down (gem8:-10). This is a SEPARATE map from account_profiles on purpose -- a bare account_profiles entry would clobber the account's inferred model-tier and silently drop it out of routing. With all weights 0 the historical session-count balancing is unchanged.\",\n \"routing\": {\n \"light_confidence\": 0.999,\n \"hard_tier1_fallback\": \"stop\"\n }\n}\n"}, "58bc4648bba1d15ba586dcd6a944f5be10058bf5": {"t": 1785846842.956205, "n": 3581, "h": "843e79d82642a5b1", "c": "{\n \"_doc\": \"Account policy for the fleet resume/switcher layer (tools/fleet_accounts.py), applying uniformly to BOTH product families -- Claude (config dirs under the user home, e.g. ~/.claude*) and opencode (config dirs under XDG config home, e.g. ~/.config/opencode*). Copy this to tools/_registry/accounts_policy.json and edit; the live file under _registry/ is gitignored so each host keeps its own roster. 'exclude' substrings are matched against the bare account tag (e.g. 'backup', 'worker-a') and the dir basename; a match tombstones that account so it is never offered as a resume target. 'include_only' (when non-empty) is an allowlist: only listed tags are workers. 'account_profiles' can override model_tier/model/effort by account basename, product:tag, tag, or product. With NO live file present, fleet_accounts.py falls back first to this example, then to its built-in defaults (which already exclude backup/breakglass accounts, infer Claude as tier 1 Opus/xhigh, GLM-5.2 opencode as tier 2, unknowns as tier 3, and recognize the ranked NVIDIA NIM coding seats opencode-nim-deepseek-v4-pro, opencode-nim-kimi-k26, and opencode-nim-glm52 as tier 1 opencode seats).\",\n \"exclude\": [\"backup\", \"breakglass\"],\n \"include_only\": [],\n \"notes\": {\n \"backup\": \"break-glass backup account; never auto-resume\"\n },\n \"account_profiles\": {},\n \"_account_profiles_example\": {\n \"claude\": {\n \"model_tier\": 1,\n \"model\": \"opus\",\n \"effort\": \"xhigh\"\n },\n \"opencode:zai2\": {\n \"model_tier\": 2,\n \"model\": \"zai-coding-plan/glm-5.2\"\n },\n \"opencode:nim-deepseek-v4-pro\": {\n \"model_tier\": 1,\n \"model\": \"deepseek-ai/deepseek-v4-pro\",\n \"agent\": \"opencode\"\n },\n \"opencode:nim-kimi-k26\": {\n \"model_tier\": 1,\n \"model\": \"moonshotai/kimi-k2.6\",\n \"agent\": \"opencode\"\n },\n \"opencode:nim-glm52\": {\n \"model_tier\": 1,\n \"model\": \"z-ai/glm-5.2\",\n \"agent\": \"opencode\"\n }\n },\n \"route_weights\": {},\n \"_route_weights_example\": {\n \"gem7\": 10,\n \"gem8\": -10,\n \"opencode:nim-deepseek-v4-pro\": 30,\n \"opencode:nim-kimi-k26\": 20,\n \"opencode:nim-glm52\": 10\n },\n \"_nvidia_nim_coding_seats\": {\n \"ranked_default_order\": [\n \"opencode-nim-deepseek-v4-pro\",\n \"opencode-nim-kimi-k26\",\n \"opencode-nim-glm52\"\n ],\n \"base_url\": \"https://integrate.api.nvidia.com/v1\",\n \"api_key_env\": \"NVIDIA_API_KEY\",\n \"caveat\": \"moonshotai/kimi-k2.6 is ranked by current coding/agentic benchmark strength but NVIDIA marks the API unsupported after 2026-07-07; set opencode:nim-kimi-k26 to weight 0 or exclude that seat if it is no longer available.\"\n },\n \"_route_weights_doc\": \"route_weights maps an account (basename, product:tag, tag, or product) to an int capacity bias (default 0) for the switcher's tie-break: HIGHER = more room -> preferred EARLIER. The fleet has NO remaining-quota telemetry (an account probe only returns OK/AUTH/LIMIT, never headroom), so the router cannot itself tell which available account has the most quota left -- it otherwise balances purely on live/active SESSION counts. Use route_weights to encode out-of-band knowledge: lift an account you know has room (gem7:+10 above) and/or push a near-capped one down (gem8:-10). This is a SEPARATE map from account_profiles on purpose -- a bare account_profiles entry would clobber the account's inferred model-tier and silently drop it out of routing. With all weights 0 the historical session-count balancing is unchanged.\",\n \"routing\": {\n \"light_confidence\": 0.999,\n \"hard_tier1_fallback\": \"stop\"\n }\n}\n"}}, "experiments/api-host-bridge/api-host-roster.json": {"13f5f1a6e0391eb28bcbea759890fb009251e5d1": {"t": 1785674489.0206127, "n": 16140, "h": "d75370f97357e132", "c": "{\n \"schema\": \"fak.api-host-roster.v1\",\n \"generated_at\": \"2026-06-18T13:12:32.966794Z\",\n \"scope_note\": \"No-spend target roster. These are operator templates for hosts that match a supported wire class; live success still requires credentials, billing, and provider access state.\",\n \"summary\": {\n \"targets\": 13,\n \"supported_templates\": 13,\n \"openai_compatible_templates\": 13,\n \"native_provider_templates\": 0,\n \"direct_templates\": 0,\n \"invalid_targets\": 0,\n \"unsupported_wire\": 0,\n \"duplicate_names\": 0,\n \"auth_required_templates\": 12,\n \"roster_gate\": true\n },\n \"targets\": [\n {\n \"name\": \"gemini_openai_compatible\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://generativelanguage.googleapis.com/v1beta/openai\",\n \"api_key_env\": \"GEMINI_API_KEY\",\n \"model_hint\": \"gemini-2.5-flash\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'gemini_openai_compatible|https://generativelanguage.googleapis.com/v1beta/openai|GEMINI_API_KEY|gemini-2.5-flash' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'gemini_openai_compatible|openai-compatible|https://generativelanguage.googleapis.com/v1beta/openai|GEMINI_API_KEY|gemini-2.5-flash' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"glama_gateway\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://gateway.glama.ai/v1\",\n \"api_key_env\": \"GLAMA_API_KEY\",\n \"model_hint\": \"openai/gpt-4.1-nano-2025-04-14\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'glama_gateway|https://gateway.glama.ai/v1|GLAMA_API_KEY|openai/gpt-4.1-nano-2025-04-14' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'glama_gateway|openai-compatible|https://gateway.glama.ai/v1|GLAMA_API_KEY|openai/gpt-4.1-nano-2025-04-14' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"pollinations_no_key\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://gen.pollinations.ai/v1\",\n \"api_key_env\": \"\",\n \"model_hint\": \"openai-fast\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": false,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'pollinations_no_key|https://gen.pollinations.ai/v1||openai-fast' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'pollinations_no_key|openai-compatible|https://gen.pollinations.ai/v1||openai-fast' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"openai_api\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://api.openai.com/v1\",\n \"api_key_env\": \"OPENAI_API_KEY\",\n \"model_hint\": \"gpt-4.1-mini\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'openai_api|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'openai_api|openai-compatible|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"xai_api\",\n \"provider\": \"xai\",\n \"base_url\": \"https://api.x.ai/v1\",\n \"api_key_env\": \"XAI_API_KEY\",\n \"model_hint\": \"grok-3-mini\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'xai_api|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'xai_api|xai|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"openrouter_gateway\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://openrouter.ai/api/v1\",\n \"api_key_env\": \"OPENROUTER_API_KEY\",\n \"model_hint\": \"openai/gpt-4.1-mini\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'openrouter_gateway|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'openrouter_gateway|openai-compatible|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"groq_openai\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://api.groq.com/openai/v1\",\n \"api_key_env\": \"GROQ_API_KEY\",\n \"model_hint\": \"llama-3.3-70b-versatile\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'groq_openai|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'groq_openai|openai-compatible|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"together_openai\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://api.together.xyz/v1\",\n \"api_key_env\": \"TOGETHER_API_KEY\",\n \"model_hint\": \"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'together_openai|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'together_openai|openai-compatible|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"mistral_openai\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://api.mistral.ai/v1\",\n \"api_key_env\": \"MISTRAL_API_KEY\",\n \"model_hint\": \"mistral-small-latest\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'mistral_openai|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'mistral_openai|openai-compatible|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"deepseek_openai\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://api.deepseek.com\",\n \"api_key_env\": \"DEEPSEEK_API_KEY\",\n \"model_hint\": \"deepseek-chat\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'deepseek_openai|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'deepseek_openai|openai-compatible|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"fireworks_openai\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://api.fireworks.ai/inference/v1\",\n \"api_key_env\": \"FIREWORKS_API_KEY\",\n \"model_hint\": \"accounts/fireworks/models/llama-v3p1-8b-instruct\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'fireworks_openai|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'fireworks_openai|openai-compatible|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"perplexity_openai\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://api.perplexity.ai\",\n \"api_key_env\": \"PERPLEXITY_API_KEY\",\n \"model_hint\": \"sonar\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'perplexity_openai|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'perplexity_openai|openai-compatible|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"cerebras_openai\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://api.cerebras.ai/v1\",\n \"api_key_env\": \"CEREBRAS_API_KEY\",\n \"model_hint\": \"llama3.1-8b\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'cerebras_openai|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'cerebras_openai|openai-compatible|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n }\n ],\n \"duplicate_names\": [],\n \"bulk_commands\": {\n \"readiness\": \"python tools/api_host_readiness_probe.py --target 'gemini_openai_compatible|https://generativelanguage.googleapis.com/v1beta/openai|GEMINI_API_KEY|gemini-2.5-flash' --target 'glama_gateway|https://gateway.glama.ai/v1|GLAMA_API_KEY|openai/gpt-4.1-nano-2025-04-14' --target 'pollinations_no_key|https://gen.pollinations.ai/v1||openai-fast' --target 'openai_api|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --target 'xai_api|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --target 'openrouter_gateway|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --target 'groq_openai|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --target 'together_openai|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --target 'mistral_openai|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --target 'deepseek_openai|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --target 'fireworks_openai|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --target 'perplexity_openai|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --target 'cerebras_openai|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance\": \"python tools/api_host_acceptance_probe.py --target 'gemini_openai_compatible|openai-compatible|https://generativelanguage.googleapis.com/v1beta/openai|GEMINI_API_KEY|gemini-2.5-flash' --target 'glama_gateway|openai-compatible|https://gateway.glama.ai/v1|GLAMA_API_KEY|openai/gpt-4.1-nano-2025-04-14' --target 'pollinations_no_key|openai-compatible|https://gen.pollinations.ai/v1||openai-fast' --target 'openai_api|openai-compatible|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --target 'xai_api|xai|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --target 'openrouter_gateway|openai-compatible|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --target 'groq_openai|openai-compatible|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --target 'together_openai|openai-compatible|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --target 'mistral_openai|openai-compatible|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --target 'deepseek_openai|openai-compatible|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --target 'fireworks_openai|openai-compatible|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --target 'perplexity_openai|openai-compatible|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --target 'cerebras_openai|openai-compatible|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n }\n}\n"}, "58bc4648bba1d15ba586dcd6a944f5be10058bf5": {"t": 1785846886.1534326, "n": 16140, "h": "d75370f97357e132", "c": "{\n \"schema\": \"fak.api-host-roster.v1\",\n \"generated_at\": \"2026-06-18T13:12:32.966794Z\",\n \"scope_note\": \"No-spend target roster. These are operator templates for hosts that match a supported wire class; live success still requires credentials, billing, and provider access state.\",\n \"summary\": {\n \"targets\": 13,\n \"supported_templates\": 13,\n \"openai_compatible_templates\": 13,\n \"native_provider_templates\": 0,\n \"direct_templates\": 0,\n \"invalid_targets\": 0,\n \"unsupported_wire\": 0,\n \"duplicate_names\": 0,\n \"auth_required_templates\": 12,\n \"roster_gate\": true\n },\n \"targets\": [\n {\n \"name\": \"gemini_openai_compatible\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://generativelanguage.googleapis.com/v1beta/openai\",\n \"api_key_env\": \"GEMINI_API_KEY\",\n \"model_hint\": \"gemini-2.5-flash\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'gemini_openai_compatible|https://generativelanguage.googleapis.com/v1beta/openai|GEMINI_API_KEY|gemini-2.5-flash' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'gemini_openai_compatible|openai-compatible|https://generativelanguage.googleapis.com/v1beta/openai|GEMINI_API_KEY|gemini-2.5-flash' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"glama_gateway\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://gateway.glama.ai/v1\",\n \"api_key_env\": \"GLAMA_API_KEY\",\n \"model_hint\": \"openai/gpt-4.1-nano-2025-04-14\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'glama_gateway|https://gateway.glama.ai/v1|GLAMA_API_KEY|openai/gpt-4.1-nano-2025-04-14' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'glama_gateway|openai-compatible|https://gateway.glama.ai/v1|GLAMA_API_KEY|openai/gpt-4.1-nano-2025-04-14' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"pollinations_no_key\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://gen.pollinations.ai/v1\",\n \"api_key_env\": \"\",\n \"model_hint\": \"openai-fast\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": false,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'pollinations_no_key|https://gen.pollinations.ai/v1||openai-fast' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'pollinations_no_key|openai-compatible|https://gen.pollinations.ai/v1||openai-fast' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"openai_api\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://api.openai.com/v1\",\n \"api_key_env\": \"OPENAI_API_KEY\",\n \"model_hint\": \"gpt-4.1-mini\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'openai_api|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'openai_api|openai-compatible|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"xai_api\",\n \"provider\": \"xai\",\n \"base_url\": \"https://api.x.ai/v1\",\n \"api_key_env\": \"XAI_API_KEY\",\n \"model_hint\": \"grok-3-mini\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'xai_api|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'xai_api|xai|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"openrouter_gateway\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://openrouter.ai/api/v1\",\n \"api_key_env\": \"OPENROUTER_API_KEY\",\n \"model_hint\": \"openai/gpt-4.1-mini\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'openrouter_gateway|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'openrouter_gateway|openai-compatible|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"groq_openai\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://api.groq.com/openai/v1\",\n \"api_key_env\": \"GROQ_API_KEY\",\n \"model_hint\": \"llama-3.3-70b-versatile\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'groq_openai|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'groq_openai|openai-compatible|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"together_openai\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://api.together.xyz/v1\",\n \"api_key_env\": \"TOGETHER_API_KEY\",\n \"model_hint\": \"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'together_openai|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'together_openai|openai-compatible|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"mistral_openai\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://api.mistral.ai/v1\",\n \"api_key_env\": \"MISTRAL_API_KEY\",\n \"model_hint\": \"mistral-small-latest\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'mistral_openai|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'mistral_openai|openai-compatible|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"deepseek_openai\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://api.deepseek.com\",\n \"api_key_env\": \"DEEPSEEK_API_KEY\",\n \"model_hint\": \"deepseek-chat\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'deepseek_openai|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'deepseek_openai|openai-compatible|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"fireworks_openai\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://api.fireworks.ai/inference/v1\",\n \"api_key_env\": \"FIREWORKS_API_KEY\",\n \"model_hint\": \"accounts/fireworks/models/llama-v3p1-8b-instruct\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'fireworks_openai|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'fireworks_openai|openai-compatible|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"perplexity_openai\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://api.perplexity.ai\",\n \"api_key_env\": \"PERPLEXITY_API_KEY\",\n \"model_hint\": \"sonar\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'perplexity_openai|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'perplexity_openai|openai-compatible|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n },\n {\n \"name\": \"cerebras_openai\",\n \"provider\": \"openai-compatible\",\n \"base_url\": \"https://api.cerebras.ai/v1\",\n \"api_key_env\": \"CEREBRAS_API_KEY\",\n \"model_hint\": \"llama3.1-8b\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"status\": \"SUPPORTED_TEMPLATE\",\n \"error\": \"\",\n \"requires_auth\": true,\n \"readiness_command\": \"python tools/api_host_readiness_probe.py --target 'cerebras_openai|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance_command\": \"python tools/api_host_acceptance_probe.py --target 'cerebras_openai|openai-compatible|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n }\n ],\n \"duplicate_names\": [],\n \"bulk_commands\": {\n \"readiness\": \"python tools/api_host_readiness_probe.py --target 'gemini_openai_compatible|https://generativelanguage.googleapis.com/v1beta/openai|GEMINI_API_KEY|gemini-2.5-flash' --target 'glama_gateway|https://gateway.glama.ai/v1|GLAMA_API_KEY|openai/gpt-4.1-nano-2025-04-14' --target 'pollinations_no_key|https://gen.pollinations.ai/v1||openai-fast' --target 'openai_api|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --target 'xai_api|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --target 'openrouter_gateway|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --target 'groq_openai|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --target 'together_openai|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --target 'mistral_openai|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --target 'deepseek_openai|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --target 'fireworks_openai|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --target 'perplexity_openai|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --target 'cerebras_openai|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"acceptance\": \"python tools/api_host_acceptance_probe.py --target 'gemini_openai_compatible|openai-compatible|https://generativelanguage.googleapis.com/v1beta/openai|GEMINI_API_KEY|gemini-2.5-flash' --target 'glama_gateway|openai-compatible|https://gateway.glama.ai/v1|GLAMA_API_KEY|openai/gpt-4.1-nano-2025-04-14' --target 'pollinations_no_key|openai-compatible|https://gen.pollinations.ai/v1||openai-fast' --target 'openai_api|openai-compatible|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --target 'xai_api|xai|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --target 'openrouter_gateway|openai-compatible|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --target 'groq_openai|openai-compatible|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --target 'together_openai|openai-compatible|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --target 'mistral_openai|openai-compatible|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --target 'deepseek_openai|openai-compatible|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --target 'fireworks_openai|openai-compatible|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --target 'perplexity_openai|openai-compatible|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --target 'cerebras_openai|openai-compatible|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\"\n }\n}\n"}}, "experiments/api-host-bridge/api-host-live-smoke-runner.json": {"f1c36fd45ecfb60dc33288bccf3477b30bbd53cf": {"t": 1785745272.4996805, "n": 15060, "h": "b67578eb150c9392", "c": "{\n \"schema\": \"fak.api-host-live-smoke-runner.v1\",\n \"generated_at\": \"2026-06-18T13:12:35.064717Z\",\n \"scope_note\": \"Execution ledger for API-host live-smoke queue rows. By default it does not spend tokens or call paid/keyed hosts; pass --execute-ready to run rows whose queue state is READY_TO_EXECUTE. The gate fails closed if ready rows are left unexecuted.\",\n \"summary\": {\n \"targets\": 13,\n \"execute_ready\": false,\n \"already_complete\": 1,\n \"ready_to_execute\": 0,\n \"ready_not_executed\": 0,\n \"executed\": 0,\n \"passed\": 0,\n \"failed\": 0,\n \"skipped_external_state\": 2,\n \"skipped_waiting_for_credential\": 10,\n \"skipped_ready_for_probe\": 0,\n \"command_gaps\": 0,\n \"unclassified\": 0,\n \"artifact_errors\": 0,\n \"ready_execution_gaps\": 0,\n \"live_smoke_runner_gate\": true\n },\n \"runs\": [\n {\n \"target\": \"gemini_openai_compatible\",\n \"queue_state\": \"COMPLETE\",\n \"operator_prerequisite\": \"none\",\n \"command_count\": 0,\n \"commands\": [],\n \"runner_status\": \"ALREADY_COMPLETE\",\n \"command_results\": []\n },\n {\n \"target\": \"glama_gateway\",\n \"queue_state\": \"BLOCKED_EXTERNAL_STATE\",\n \"operator_prerequisite\": \"Attach billing/payment method for the account behind GLAMA_API_KEY.\",\n \"command_count\": 1,\n \"commands\": [\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-glama-gateway-retry -ApiBaseUrl 'https://gateway.glama.ai/v1' -ApiKeyEnv GLAMA_API_KEY -ApiModels 'openai/gpt-4.1-nano-2025-04-14' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_EXTERNAL_STATE\",\n \"command_results\": []\n },\n {\n \"target\": \"pollinations_no_key\",\n \"queue_state\": \"BLOCKED_EXTERNAL_STATE\",\n \"operator_prerequisite\": \"Configure a valid bearer token or access path via POLLINATIONS_API_KEY.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'pollinations_no_key|https://gen.pollinations.ai/v1|POLLINATIONS_API_KEY|openai-fast' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'pollinations_no_key|openai-compatible|https://gen.pollinations.ai/v1|POLLINATIONS_API_KEY|openai-fast' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-pollinations-no-key-retry -ApiBaseUrl 'https://gen.pollinations.ai/v1' -ApiKeyEnv POLLINATIONS_API_KEY -ApiModels 'openai-fast' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_EXTERNAL_STATE\",\n \"command_results\": []\n },\n {\n \"target\": \"openai_api\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set OPENAI_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'openai_api|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'openai_api|openai-compatible|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-openai-api-retry -ApiBaseUrl 'https://api.openai.com/v1' -ApiKeyEnv OPENAI_API_KEY -ApiModels 'gpt-4.1-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"xai_api\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set XAI_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'xai_api|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'xai_api|xai|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-xai-api-retry -ApiBaseUrl 'https://api.x.ai/v1' -ApiKeyEnv XAI_API_KEY -ApiModels 'grok-3-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"openrouter_gateway\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set OPENROUTER_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'openrouter_gateway|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'openrouter_gateway|openai-compatible|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-openrouter-gateway-retry -ApiBaseUrl 'https://openrouter.ai/api/v1' -ApiKeyEnv OPENROUTER_API_KEY -ApiModels 'openai/gpt-4.1-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"groq_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set GROQ_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'groq_openai|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'groq_openai|openai-compatible|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-groq-openai-retry -ApiBaseUrl 'https://api.groq.com/openai/v1' -ApiKeyEnv GROQ_API_KEY -ApiModels 'llama-3.3-70b-versatile' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"together_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set TOGETHER_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'together_openai|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'together_openai|openai-compatible|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-together-openai-retry -ApiBaseUrl 'https://api.together.xyz/v1' -ApiKeyEnv TOGETHER_API_KEY -ApiModels 'meta-llama/Llama-3.3-70B-Instruct-Turbo' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"mistral_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set MISTRAL_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'mistral_openai|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'mistral_openai|openai-compatible|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-mistral-openai-retry -ApiBaseUrl 'https://api.mistral.ai/v1' -ApiKeyEnv MISTRAL_API_KEY -ApiModels 'mistral-small-latest' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"deepseek_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set DEEPSEEK_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'deepseek_openai|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'deepseek_openai|openai-compatible|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-deepseek-openai-retry -ApiBaseUrl 'https://api.deepseek.com' -ApiKeyEnv DEEPSEEK_API_KEY -ApiModels 'deepseek-chat' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"fireworks_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set FIREWORKS_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'fireworks_openai|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'fireworks_openai|openai-compatible|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-fireworks-openai-retry -ApiBaseUrl 'https://api.fireworks.ai/inference/v1' -ApiKeyEnv FIREWORKS_API_KEY -ApiModels 'accounts/fireworks/models/llama-v3p1-8b-instruct' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"perplexity_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set PERPLEXITY_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'perplexity_openai|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'perplexity_openai|openai-compatible|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-perplexity-openai-retry -ApiBaseUrl 'https://api.perplexity.ai' -ApiKeyEnv PERPLEXITY_API_KEY -ApiModels 'sonar' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"cerebras_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set CEREBRAS_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'cerebras_openai|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'cerebras_openai|openai-compatible|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-cerebras-openai-retry -ApiBaseUrl 'https://api.cerebras.ai/v1' -ApiKeyEnv CEREBRAS_API_KEY -ApiModels 'llama3.1-8b' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n }\n ],\n \"artifact_errors\": {},\n \"artifacts\": {\n \"queue\": \"fak/experiments/api-host-bridge/api-host-live-smoke-queue.json\"\n },\n \"queue_summary\": {\n \"targets\": 13,\n \"complete\": 1,\n \"ready_to_execute\": 0,\n \"blocked_external_state\": 2,\n \"waiting_for_credential\": 10,\n \"ready_for_probe\": 0,\n \"unqualified\": 0,\n \"unclassified\": 0,\n \"command_gaps\": 0,\n \"commands\": 34,\n \"artifact_errors\": 0,\n \"live_smoke_queue_gate\": true\n }\n}\n"}, "337033d0aad94cc5c296341a8b6490bdf8c379d0": {"t": 1785749570.1589634, "n": 15060, "h": "b67578eb150c9392", "c": "{\n \"schema\": \"fak.api-host-live-smoke-runner.v1\",\n \"generated_at\": \"2026-06-18T13:12:35.064717Z\",\n \"scope_note\": \"Execution ledger for API-host live-smoke queue rows. By default it does not spend tokens or call paid/keyed hosts; pass --execute-ready to run rows whose queue state is READY_TO_EXECUTE. The gate fails closed if ready rows are left unexecuted.\",\n \"summary\": {\n \"targets\": 13,\n \"execute_ready\": false,\n \"already_complete\": 1,\n \"ready_to_execute\": 0,\n \"ready_not_executed\": 0,\n \"executed\": 0,\n \"passed\": 0,\n \"failed\": 0,\n \"skipped_external_state\": 2,\n \"skipped_waiting_for_credential\": 10,\n \"skipped_ready_for_probe\": 0,\n \"command_gaps\": 0,\n \"unclassified\": 0,\n \"artifact_errors\": 0,\n \"ready_execution_gaps\": 0,\n \"live_smoke_runner_gate\": true\n },\n \"runs\": [\n {\n \"target\": \"gemini_openai_compatible\",\n \"queue_state\": \"COMPLETE\",\n \"operator_prerequisite\": \"none\",\n \"command_count\": 0,\n \"commands\": [],\n \"runner_status\": \"ALREADY_COMPLETE\",\n \"command_results\": []\n },\n {\n \"target\": \"glama_gateway\",\n \"queue_state\": \"BLOCKED_EXTERNAL_STATE\",\n \"operator_prerequisite\": \"Attach billing/payment method for the account behind GLAMA_API_KEY.\",\n \"command_count\": 1,\n \"commands\": [\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-glama-gateway-retry -ApiBaseUrl 'https://gateway.glama.ai/v1' -ApiKeyEnv GLAMA_API_KEY -ApiModels 'openai/gpt-4.1-nano-2025-04-14' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_EXTERNAL_STATE\",\n \"command_results\": []\n },\n {\n \"target\": \"pollinations_no_key\",\n \"queue_state\": \"BLOCKED_EXTERNAL_STATE\",\n \"operator_prerequisite\": \"Configure a valid bearer token or access path via POLLINATIONS_API_KEY.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'pollinations_no_key|https://gen.pollinations.ai/v1|POLLINATIONS_API_KEY|openai-fast' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'pollinations_no_key|openai-compatible|https://gen.pollinations.ai/v1|POLLINATIONS_API_KEY|openai-fast' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-pollinations-no-key-retry -ApiBaseUrl 'https://gen.pollinations.ai/v1' -ApiKeyEnv POLLINATIONS_API_KEY -ApiModels 'openai-fast' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_EXTERNAL_STATE\",\n \"command_results\": []\n },\n {\n \"target\": \"openai_api\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set OPENAI_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'openai_api|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'openai_api|openai-compatible|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-openai-api-retry -ApiBaseUrl 'https://api.openai.com/v1' -ApiKeyEnv OPENAI_API_KEY -ApiModels 'gpt-4.1-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"xai_api\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set XAI_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'xai_api|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'xai_api|xai|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-xai-api-retry -ApiBaseUrl 'https://api.x.ai/v1' -ApiKeyEnv XAI_API_KEY -ApiModels 'grok-3-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"openrouter_gateway\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set OPENROUTER_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'openrouter_gateway|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'openrouter_gateway|openai-compatible|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-openrouter-gateway-retry -ApiBaseUrl 'https://openrouter.ai/api/v1' -ApiKeyEnv OPENROUTER_API_KEY -ApiModels 'openai/gpt-4.1-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"groq_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set GROQ_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'groq_openai|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'groq_openai|openai-compatible|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-groq-openai-retry -ApiBaseUrl 'https://api.groq.com/openai/v1' -ApiKeyEnv GROQ_API_KEY -ApiModels 'llama-3.3-70b-versatile' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"together_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set TOGETHER_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'together_openai|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'together_openai|openai-compatible|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-together-openai-retry -ApiBaseUrl 'https://api.together.xyz/v1' -ApiKeyEnv TOGETHER_API_KEY -ApiModels 'meta-llama/Llama-3.3-70B-Instruct-Turbo' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"mistral_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set MISTRAL_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'mistral_openai|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'mistral_openai|openai-compatible|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-mistral-openai-retry -ApiBaseUrl 'https://api.mistral.ai/v1' -ApiKeyEnv MISTRAL_API_KEY -ApiModels 'mistral-small-latest' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"deepseek_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set DEEPSEEK_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'deepseek_openai|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'deepseek_openai|openai-compatible|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-deepseek-openai-retry -ApiBaseUrl 'https://api.deepseek.com' -ApiKeyEnv DEEPSEEK_API_KEY -ApiModels 'deepseek-chat' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"fireworks_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set FIREWORKS_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'fireworks_openai|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'fireworks_openai|openai-compatible|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-fireworks-openai-retry -ApiBaseUrl 'https://api.fireworks.ai/inference/v1' -ApiKeyEnv FIREWORKS_API_KEY -ApiModels 'accounts/fireworks/models/llama-v3p1-8b-instruct' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"perplexity_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set PERPLEXITY_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'perplexity_openai|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'perplexity_openai|openai-compatible|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-perplexity-openai-retry -ApiBaseUrl 'https://api.perplexity.ai' -ApiKeyEnv PERPLEXITY_API_KEY -ApiModels 'sonar' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"cerebras_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set CEREBRAS_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'cerebras_openai|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'cerebras_openai|openai-compatible|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-cerebras-openai-retry -ApiBaseUrl 'https://api.cerebras.ai/v1' -ApiKeyEnv CEREBRAS_API_KEY -ApiModels 'llama3.1-8b' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n }\n ],\n \"artifact_errors\": {},\n \"artifacts\": {\n \"queue\": \"fak/experiments/api-host-bridge/api-host-live-smoke-queue.json\"\n },\n \"queue_summary\": {\n \"targets\": 13,\n \"complete\": 1,\n \"ready_to_execute\": 0,\n \"blocked_external_state\": 2,\n \"waiting_for_credential\": 10,\n \"ready_for_probe\": 0,\n \"unqualified\": 0,\n \"unclassified\": 0,\n \"command_gaps\": 0,\n \"commands\": 34,\n \"artifact_errors\": 0,\n \"live_smoke_queue_gate\": true\n }\n}\n"}, "13f5f1a6e0391eb28bcbea759890fb009251e5d1": {"t": 1786067334.376923, "n": 15060, "h": "b67578eb150c9392", "c": "{\n \"schema\": \"fak.api-host-live-smoke-runner.v1\",\n \"generated_at\": \"2026-06-18T13:12:35.064717Z\",\n \"scope_note\": \"Execution ledger for API-host live-smoke queue rows. By default it does not spend tokens or call paid/keyed hosts; pass --execute-ready to run rows whose queue state is READY_TO_EXECUTE. The gate fails closed if ready rows are left unexecuted.\",\n \"summary\": {\n \"targets\": 13,\n \"execute_ready\": false,\n \"already_complete\": 1,\n \"ready_to_execute\": 0,\n \"ready_not_executed\": 0,\n \"executed\": 0,\n \"passed\": 0,\n \"failed\": 0,\n \"skipped_external_state\": 2,\n \"skipped_waiting_for_credential\": 10,\n \"skipped_ready_for_probe\": 0,\n \"command_gaps\": 0,\n \"unclassified\": 0,\n \"artifact_errors\": 0,\n \"ready_execution_gaps\": 0,\n \"live_smoke_runner_gate\": true\n },\n \"runs\": [\n {\n \"target\": \"gemini_openai_compatible\",\n \"queue_state\": \"COMPLETE\",\n \"operator_prerequisite\": \"none\",\n \"command_count\": 0,\n \"commands\": [],\n \"runner_status\": \"ALREADY_COMPLETE\",\n \"command_results\": []\n },\n {\n \"target\": \"glama_gateway\",\n \"queue_state\": \"BLOCKED_EXTERNAL_STATE\",\n \"operator_prerequisite\": \"Attach billing/payment method for the account behind GLAMA_API_KEY.\",\n \"command_count\": 1,\n \"commands\": [\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-glama-gateway-retry -ApiBaseUrl 'https://gateway.glama.ai/v1' -ApiKeyEnv GLAMA_API_KEY -ApiModels 'openai/gpt-4.1-nano-2025-04-14' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_EXTERNAL_STATE\",\n \"command_results\": []\n },\n {\n \"target\": \"pollinations_no_key\",\n \"queue_state\": \"BLOCKED_EXTERNAL_STATE\",\n \"operator_prerequisite\": \"Configure a valid bearer token or access path via POLLINATIONS_API_KEY.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'pollinations_no_key|https://gen.pollinations.ai/v1|POLLINATIONS_API_KEY|openai-fast' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'pollinations_no_key|openai-compatible|https://gen.pollinations.ai/v1|POLLINATIONS_API_KEY|openai-fast' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-pollinations-no-key-retry -ApiBaseUrl 'https://gen.pollinations.ai/v1' -ApiKeyEnv POLLINATIONS_API_KEY -ApiModels 'openai-fast' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_EXTERNAL_STATE\",\n \"command_results\": []\n },\n {\n \"target\": \"openai_api\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set OPENAI_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'openai_api|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'openai_api|openai-compatible|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-openai-api-retry -ApiBaseUrl 'https://api.openai.com/v1' -ApiKeyEnv OPENAI_API_KEY -ApiModels 'gpt-4.1-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"xai_api\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set XAI_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'xai_api|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'xai_api|xai|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-xai-api-retry -ApiBaseUrl 'https://api.x.ai/v1' -ApiKeyEnv XAI_API_KEY -ApiModels 'grok-3-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"openrouter_gateway\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set OPENROUTER_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'openrouter_gateway|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'openrouter_gateway|openai-compatible|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-openrouter-gateway-retry -ApiBaseUrl 'https://openrouter.ai/api/v1' -ApiKeyEnv OPENROUTER_API_KEY -ApiModels 'openai/gpt-4.1-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"groq_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set GROQ_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'groq_openai|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'groq_openai|openai-compatible|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-groq-openai-retry -ApiBaseUrl 'https://api.groq.com/openai/v1' -ApiKeyEnv GROQ_API_KEY -ApiModels 'llama-3.3-70b-versatile' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"together_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set TOGETHER_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'together_openai|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'together_openai|openai-compatible|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-together-openai-retry -ApiBaseUrl 'https://api.together.xyz/v1' -ApiKeyEnv TOGETHER_API_KEY -ApiModels 'meta-llama/Llama-3.3-70B-Instruct-Turbo' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"mistral_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set MISTRAL_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'mistral_openai|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'mistral_openai|openai-compatible|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-mistral-openai-retry -ApiBaseUrl 'https://api.mistral.ai/v1' -ApiKeyEnv MISTRAL_API_KEY -ApiModels 'mistral-small-latest' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"deepseek_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set DEEPSEEK_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'deepseek_openai|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'deepseek_openai|openai-compatible|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-deepseek-openai-retry -ApiBaseUrl 'https://api.deepseek.com' -ApiKeyEnv DEEPSEEK_API_KEY -ApiModels 'deepseek-chat' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"fireworks_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set FIREWORKS_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'fireworks_openai|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'fireworks_openai|openai-compatible|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-fireworks-openai-retry -ApiBaseUrl 'https://api.fireworks.ai/inference/v1' -ApiKeyEnv FIREWORKS_API_KEY -ApiModels 'accounts/fireworks/models/llama-v3p1-8b-instruct' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"perplexity_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set PERPLEXITY_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'perplexity_openai|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'perplexity_openai|openai-compatible|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-perplexity-openai-retry -ApiBaseUrl 'https://api.perplexity.ai' -ApiKeyEnv PERPLEXITY_API_KEY -ApiModels 'sonar' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"cerebras_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set CEREBRAS_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'cerebras_openai|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'cerebras_openai|openai-compatible|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-cerebras-openai-retry -ApiBaseUrl 'https://api.cerebras.ai/v1' -ApiKeyEnv CEREBRAS_API_KEY -ApiModels 'llama3.1-8b' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n }\n ],\n \"artifact_errors\": {},\n \"artifacts\": {\n \"queue\": \"fak/experiments/api-host-bridge/api-host-live-smoke-queue.json\"\n },\n \"queue_summary\": {\n \"targets\": 13,\n \"complete\": 1,\n \"ready_to_execute\": 0,\n \"blocked_external_state\": 2,\n \"waiting_for_credential\": 10,\n \"ready_for_probe\": 0,\n \"unqualified\": 0,\n \"unclassified\": 0,\n \"command_gaps\": 0,\n \"commands\": 34,\n \"artifact_errors\": 0,\n \"live_smoke_queue_gate\": true\n }\n}\n"}, "394f65f0a4c27e2205fc5e26a67a720b18fdba5a": {"t": 1786068226.7975285, "n": 15060, "h": "b67578eb150c9392", "c": "{\n \"schema\": \"fak.api-host-live-smoke-runner.v1\",\n \"generated_at\": \"2026-06-18T13:12:35.064717Z\",\n \"scope_note\": \"Execution ledger for API-host live-smoke queue rows. By default it does not spend tokens or call paid/keyed hosts; pass --execute-ready to run rows whose queue state is READY_TO_EXECUTE. The gate fails closed if ready rows are left unexecuted.\",\n \"summary\": {\n \"targets\": 13,\n \"execute_ready\": false,\n \"already_complete\": 1,\n \"ready_to_execute\": 0,\n \"ready_not_executed\": 0,\n \"executed\": 0,\n \"passed\": 0,\n \"failed\": 0,\n \"skipped_external_state\": 2,\n \"skipped_waiting_for_credential\": 10,\n \"skipped_ready_for_probe\": 0,\n \"command_gaps\": 0,\n \"unclassified\": 0,\n \"artifact_errors\": 0,\n \"ready_execution_gaps\": 0,\n \"live_smoke_runner_gate\": true\n },\n \"runs\": [\n {\n \"target\": \"gemini_openai_compatible\",\n \"queue_state\": \"COMPLETE\",\n \"operator_prerequisite\": \"none\",\n \"command_count\": 0,\n \"commands\": [],\n \"runner_status\": \"ALREADY_COMPLETE\",\n \"command_results\": []\n },\n {\n \"target\": \"glama_gateway\",\n \"queue_state\": \"BLOCKED_EXTERNAL_STATE\",\n \"operator_prerequisite\": \"Attach billing/payment method for the account behind GLAMA_API_KEY.\",\n \"command_count\": 1,\n \"commands\": [\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-glama-gateway-retry -ApiBaseUrl 'https://gateway.glama.ai/v1' -ApiKeyEnv GLAMA_API_KEY -ApiModels 'openai/gpt-4.1-nano-2025-04-14' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_EXTERNAL_STATE\",\n \"command_results\": []\n },\n {\n \"target\": \"pollinations_no_key\",\n \"queue_state\": \"BLOCKED_EXTERNAL_STATE\",\n \"operator_prerequisite\": \"Configure a valid bearer token or access path via POLLINATIONS_API_KEY.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'pollinations_no_key|https://gen.pollinations.ai/v1|POLLINATIONS_API_KEY|openai-fast' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'pollinations_no_key|openai-compatible|https://gen.pollinations.ai/v1|POLLINATIONS_API_KEY|openai-fast' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-pollinations-no-key-retry -ApiBaseUrl 'https://gen.pollinations.ai/v1' -ApiKeyEnv POLLINATIONS_API_KEY -ApiModels 'openai-fast' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_EXTERNAL_STATE\",\n \"command_results\": []\n },\n {\n \"target\": \"openai_api\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set OPENAI_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'openai_api|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'openai_api|openai-compatible|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-openai-api-retry -ApiBaseUrl 'https://api.openai.com/v1' -ApiKeyEnv OPENAI_API_KEY -ApiModels 'gpt-4.1-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"xai_api\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set XAI_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'xai_api|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'xai_api|xai|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-xai-api-retry -ApiBaseUrl 'https://api.x.ai/v1' -ApiKeyEnv XAI_API_KEY -ApiModels 'grok-3-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"openrouter_gateway\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set OPENROUTER_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'openrouter_gateway|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'openrouter_gateway|openai-compatible|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-openrouter-gateway-retry -ApiBaseUrl 'https://openrouter.ai/api/v1' -ApiKeyEnv OPENROUTER_API_KEY -ApiModels 'openai/gpt-4.1-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"groq_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set GROQ_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'groq_openai|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'groq_openai|openai-compatible|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-groq-openai-retry -ApiBaseUrl 'https://api.groq.com/openai/v1' -ApiKeyEnv GROQ_API_KEY -ApiModels 'llama-3.3-70b-versatile' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"together_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set TOGETHER_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'together_openai|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'together_openai|openai-compatible|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-together-openai-retry -ApiBaseUrl 'https://api.together.xyz/v1' -ApiKeyEnv TOGETHER_API_KEY -ApiModels 'meta-llama/Llama-3.3-70B-Instruct-Turbo' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"mistral_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set MISTRAL_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'mistral_openai|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'mistral_openai|openai-compatible|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-mistral-openai-retry -ApiBaseUrl 'https://api.mistral.ai/v1' -ApiKeyEnv MISTRAL_API_KEY -ApiModels 'mistral-small-latest' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"deepseek_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set DEEPSEEK_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'deepseek_openai|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'deepseek_openai|openai-compatible|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-deepseek-openai-retry -ApiBaseUrl 'https://api.deepseek.com' -ApiKeyEnv DEEPSEEK_API_KEY -ApiModels 'deepseek-chat' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"fireworks_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set FIREWORKS_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'fireworks_openai|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'fireworks_openai|openai-compatible|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-fireworks-openai-retry -ApiBaseUrl 'https://api.fireworks.ai/inference/v1' -ApiKeyEnv FIREWORKS_API_KEY -ApiModels 'accounts/fireworks/models/llama-v3p1-8b-instruct' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"perplexity_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set PERPLEXITY_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'perplexity_openai|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'perplexity_openai|openai-compatible|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-perplexity-openai-retry -ApiBaseUrl 'https://api.perplexity.ai' -ApiKeyEnv PERPLEXITY_API_KEY -ApiModels 'sonar' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n },\n {\n \"target\": \"cerebras_openai\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set CEREBRAS_API_KEY for this host.\",\n \"command_count\": 3,\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'cerebras_openai|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'cerebras_openai|openai-compatible|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-cerebras-openai-retry -ApiBaseUrl 'https://api.cerebras.ai/v1' -ApiKeyEnv CEREBRAS_API_KEY -ApiModels 'llama3.1-8b' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"runner_status\": \"SKIPPED_WAITING_FOR_CREDENTIAL\",\n \"command_results\": []\n }\n ],\n \"artifact_errors\": {},\n \"artifacts\": {\n \"queue\": \"fak/experiments/api-host-bridge/api-host-live-smoke-queue.json\"\n },\n \"queue_summary\": {\n \"targets\": 13,\n \"complete\": 1,\n \"ready_to_execute\": 0,\n \"blocked_external_state\": 2,\n \"waiting_for_credential\": 10,\n \"ready_for_probe\": 0,\n \"unqualified\": 0,\n \"unclassified\": 0,\n \"command_gaps\": 0,\n \"commands\": 34,\n \"artifact_errors\": 0,\n \"live_smoke_queue_gate\": true\n }\n}\n"}}, "experiments/api-host-bridge/api-host-live-smoke-queue.json": {"f1c36fd45ecfb60dc33288bccf3477b30bbd53cf": {"t": 1785745290.0045986, "n": 22871, "h": "889a636b61537247", "c": "{\n \"schema\": \"fak.api-host-live-smoke-queue.v1\",\n \"generated_at\": \"2026-06-18T13:12:34.948345Z\",\n \"scope_note\": \"Credential-conditioned queue for completing live API-host smoke coverage. It lists executable commands and external prerequisites; it does not run paid/keyed hosts or mark external billing/auth/access state as solved.\",\n \"summary\": {\n \"targets\": 13,\n \"complete\": 1,\n \"ready_to_execute\": 0,\n \"blocked_external_state\": 2,\n \"waiting_for_credential\": 10,\n \"ready_for_probe\": 0,\n \"unqualified\": 0,\n \"unclassified\": 0,\n \"command_gaps\": 0,\n \"commands\": 34,\n \"artifact_errors\": 0,\n \"live_smoke_queue_gate\": true\n },\n \"queue\": [\n {\n \"target\": \"gemini_openai_compatible\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://generativelanguage.googleapis.com/v1beta/openai\",\n \"model_hint\": \"gemini-2.5-flash\",\n \"api_key_env\": \"GEMINI_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_LIVE_CONFIRMED\",\n \"evidence_state\": \"LIVE_CONFIRMED\",\n \"external_state_status\": \"LIVE_CONFIRMED\",\n \"queue_state\": \"COMPLETE\",\n \"operator_prerequisite\": \"none\",\n \"commands\": [],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Committed evidence already confirms a live bridge run for this host/base URL.\"\n },\n {\n \"target\": \"glama_gateway\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://gateway.glama.ai/v1\",\n \"model_hint\": \"openai/gpt-4.1-nano-2025-04-14\",\n \"api_key_env\": \"GLAMA_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_EXTERNAL_BLOCKER\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"BLOCKED_BILLING\",\n \"queue_state\": \"BLOCKED_EXTERNAL_STATE\",\n \"operator_prerequisite\": \"Attach billing/payment method for the account behind GLAMA_API_KEY.\",\n \"commands\": [\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-glama-gateway-retry -ApiBaseUrl 'https://gateway.glama.ai/v1' -ApiKeyEnv GLAMA_API_KEY -ApiModels 'openai/gpt-4.1-nano-2025-04-14' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"fix_billing_then_smoke\",\n \"retry_status\": \"BILLING_REQUIRED\",\n \"latest_sweep\": {\n \"summary_path\": \"fak/experiments/agent-live/transcript-adapter-sweep-glama-live-smoke/sweep-summary.json\",\n \"status\": \"failed\",\n \"model\": \"openai/gpt-4.1-nano-2025-04-14\",\n \"error_excerpt\": \"fak: fak arm turn 1: planner: HTTP 402: {\\\"error\\\":{\\\"code\\\":\\\"no_payment_method\\\",\\\"message\\\":\\\"No payment method\\\"}}\",\n \"transcript_sha\": null\n },\n \"next_evidence_needed\": \"Attach billing/payment method for the account behind GLAMA_API_KEY.\"\n },\n {\n \"target\": \"pollinations_no_key\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://gen.pollinations.ai/v1\",\n \"model_hint\": \"openai-fast\",\n \"api_key_env\": \"POLLINATIONS_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_EXTERNAL_BLOCKER\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"BLOCKED_AUTH\",\n \"queue_state\": \"BLOCKED_EXTERNAL_STATE\",\n \"operator_prerequisite\": \"Configure a valid bearer token or access path via POLLINATIONS_API_KEY.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'pollinations_no_key|https://gen.pollinations.ai/v1|POLLINATIONS_API_KEY|openai-fast' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'pollinations_no_key|openai-compatible|https://gen.pollinations.ai/v1|POLLINATIONS_API_KEY|openai-fast' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-pollinations-no-key-retry -ApiBaseUrl 'https://gen.pollinations.ai/v1' -ApiKeyEnv POLLINATIONS_API_KEY -ApiModels 'openai-fast' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"configure_access_then_probe_and_smoke\",\n \"retry_status\": \"AUTH_REQUIRED\",\n \"latest_sweep\": {\n \"summary_path\": \"fak/experiments/agent-live/transcript-adapter-sweep-pollinations/sweep-summary.json\",\n \"status\": \"failed\",\n \"model\": \"openai-fast\",\n \"error_excerpt\": \"fak agent: env NONE_LOCAL is empty \\u2014 proceeding with no auth header (fine for a local endpoint)\\r\\nfak: fak arm turn 1: planner: HTTP 401: {\\\"success\\\":false,\\\"error\\\":{\\\"message\\\":\\\"Authentication required. Please provide an API key via Authorizati\",\n \"transcript_sha\": null\n },\n \"next_evidence_needed\": \"Configure a valid bearer token or access path via POLLINATIONS_API_KEY.\"\n },\n {\n \"target\": \"openai_api\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.openai.com/v1\",\n \"model_hint\": \"gpt-4.1-mini\",\n \"api_key_env\": \"OPENAI_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set OPENAI_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'openai_api|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'openai_api|openai-compatible|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-openai-api-retry -ApiBaseUrl 'https://api.openai.com/v1' -ApiKeyEnv OPENAI_API_KEY -ApiModels 'gpt-4.1-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set OPENAI_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"xai_api\",\n \"provider\": \"xai\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.x.ai/v1\",\n \"model_hint\": \"grok-3-mini\",\n \"api_key_env\": \"XAI_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set XAI_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'xai_api|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'xai_api|xai|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-xai-api-retry -ApiBaseUrl 'https://api.x.ai/v1' -ApiKeyEnv XAI_API_KEY -ApiModels 'grok-3-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set XAI_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"openrouter_gateway\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://openrouter.ai/api/v1\",\n \"model_hint\": \"openai/gpt-4.1-mini\",\n \"api_key_env\": \"OPENROUTER_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set OPENROUTER_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'openrouter_gateway|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'openrouter_gateway|openai-compatible|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-openrouter-gateway-retry -ApiBaseUrl 'https://openrouter.ai/api/v1' -ApiKeyEnv OPENROUTER_API_KEY -ApiModels 'openai/gpt-4.1-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set OPENROUTER_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"groq_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.groq.com/openai/v1\",\n \"model_hint\": \"llama-3.3-70b-versatile\",\n \"api_key_env\": \"GROQ_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set GROQ_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'groq_openai|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'groq_openai|openai-compatible|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-groq-openai-retry -ApiBaseUrl 'https://api.groq.com/openai/v1' -ApiKeyEnv GROQ_API_KEY -ApiModels 'llama-3.3-70b-versatile' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set GROQ_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"together_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.together.xyz/v1\",\n \"model_hint\": \"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\n \"api_key_env\": \"TOGETHER_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set TOGETHER_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'together_openai|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'together_openai|openai-compatible|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-together-openai-retry -ApiBaseUrl 'https://api.together.xyz/v1' -ApiKeyEnv TOGETHER_API_KEY -ApiModels 'meta-llama/Llama-3.3-70B-Instruct-Turbo' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set TOGETHER_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"mistral_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.mistral.ai/v1\",\n \"model_hint\": \"mistral-small-latest\",\n \"api_key_env\": \"MISTRAL_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set MISTRAL_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'mistral_openai|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'mistral_openai|openai-compatible|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-mistral-openai-retry -ApiBaseUrl 'https://api.mistral.ai/v1' -ApiKeyEnv MISTRAL_API_KEY -ApiModels 'mistral-small-latest' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set MISTRAL_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"deepseek_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.deepseek.com\",\n \"model_hint\": \"deepseek-chat\",\n \"api_key_env\": \"DEEPSEEK_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set DEEPSEEK_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'deepseek_openai|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'deepseek_openai|openai-compatible|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-deepseek-openai-retry -ApiBaseUrl 'https://api.deepseek.com' -ApiKeyEnv DEEPSEEK_API_KEY -ApiModels 'deepseek-chat' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set DEEPSEEK_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"fireworks_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.fireworks.ai/inference/v1\",\n \"model_hint\": \"accounts/fireworks/models/llama-v3p1-8b-instruct\",\n \"api_key_env\": \"FIREWORKS_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set FIREWORKS_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'fireworks_openai|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'fireworks_openai|openai-compatible|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-fireworks-openai-retry -ApiBaseUrl 'https://api.fireworks.ai/inference/v1' -ApiKeyEnv FIREWORKS_API_KEY -ApiModels 'accounts/fireworks/models/llama-v3p1-8b-instruct' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set FIREWORKS_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"perplexity_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.perplexity.ai\",\n \"model_hint\": \"sonar\",\n \"api_key_env\": \"PERPLEXITY_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set PERPLEXITY_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'perplexity_openai|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'perplexity_openai|openai-compatible|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-perplexity-openai-retry -ApiBaseUrl 'https://api.perplexity.ai' -ApiKeyEnv PERPLEXITY_API_KEY -ApiModels 'sonar' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set PERPLEXITY_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"cerebras_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.cerebras.ai/v1\",\n \"model_hint\": \"llama3.1-8b\",\n \"api_key_env\": \"CEREBRAS_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set CEREBRAS_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'cerebras_openai|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'cerebras_openai|openai-compatible|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-cerebras-openai-retry -ApiBaseUrl 'https://api.cerebras.ai/v1' -ApiKeyEnv CEREBRAS_API_KEY -ApiModels 'llama3.1-8b' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set CEREBRAS_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n }\n ],\n \"artifact_errors\": {},\n \"artifacts\": {\n \"qualification\": \"fak/experiments/api-host-bridge/api-host-qualification.json\",\n \"retry\": \"fak/experiments/api-host-bridge/api-host-retry-packet.json\"\n },\n \"qualification_summary\": {\n \"targets\": 13,\n \"in_contract_targets\": 13,\n \"live_confirmed\": 1,\n \"ready_for_live_smoke\": 0,\n \"external_blocked\": 2,\n \"needs_credential\": 10,\n \"needs_probe\": 0,\n \"out_of_contract\": 0,\n \"invalid_targets\": 0,\n \"unclassified\": 0,\n \"artifact_errors\": 0,\n \"qualification_gate\": true\n },\n \"retry_summary\": {\n \"targets\": 13,\n \"actionable_blockers\": 13,\n \"ready_for_live_bridge_run\": 0,\n \"live_bridge_confirmed\": 0,\n \"unsupported_wire\": 0,\n \"invalid_targets\": 0,\n \"shape_mismatch\": 0,\n \"unclassified\": 0,\n \"action_gaps\": 0,\n \"artifact_errors\": 0,\n \"commands\": 37,\n \"retry_packet_gate\": true\n }\n}\n"}, "337033d0aad94cc5c296341a8b6490bdf8c379d0": {"t": 1785749567.3463979, "n": 22871, "h": "889a636b61537247", "c": "{\n \"schema\": \"fak.api-host-live-smoke-queue.v1\",\n \"generated_at\": \"2026-06-18T13:12:34.948345Z\",\n \"scope_note\": \"Credential-conditioned queue for completing live API-host smoke coverage. It lists executable commands and external prerequisites; it does not run paid/keyed hosts or mark external billing/auth/access state as solved.\",\n \"summary\": {\n \"targets\": 13,\n \"complete\": 1,\n \"ready_to_execute\": 0,\n \"blocked_external_state\": 2,\n \"waiting_for_credential\": 10,\n \"ready_for_probe\": 0,\n \"unqualified\": 0,\n \"unclassified\": 0,\n \"command_gaps\": 0,\n \"commands\": 34,\n \"artifact_errors\": 0,\n \"live_smoke_queue_gate\": true\n },\n \"queue\": [\n {\n \"target\": \"gemini_openai_compatible\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://generativelanguage.googleapis.com/v1beta/openai\",\n \"model_hint\": \"gemini-2.5-flash\",\n \"api_key_env\": \"GEMINI_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_LIVE_CONFIRMED\",\n \"evidence_state\": \"LIVE_CONFIRMED\",\n \"external_state_status\": \"LIVE_CONFIRMED\",\n \"queue_state\": \"COMPLETE\",\n \"operator_prerequisite\": \"none\",\n \"commands\": [],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Committed evidence already confirms a live bridge run for this host/base URL.\"\n },\n {\n \"target\": \"glama_gateway\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://gateway.glama.ai/v1\",\n \"model_hint\": \"openai/gpt-4.1-nano-2025-04-14\",\n \"api_key_env\": \"GLAMA_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_EXTERNAL_BLOCKER\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"BLOCKED_BILLING\",\n \"queue_state\": \"BLOCKED_EXTERNAL_STATE\",\n \"operator_prerequisite\": \"Attach billing/payment method for the account behind GLAMA_API_KEY.\",\n \"commands\": [\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-glama-gateway-retry -ApiBaseUrl 'https://gateway.glama.ai/v1' -ApiKeyEnv GLAMA_API_KEY -ApiModels 'openai/gpt-4.1-nano-2025-04-14' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"fix_billing_then_smoke\",\n \"retry_status\": \"BILLING_REQUIRED\",\n \"latest_sweep\": {\n \"summary_path\": \"fak/experiments/agent-live/transcript-adapter-sweep-glama-live-smoke/sweep-summary.json\",\n \"status\": \"failed\",\n \"model\": \"openai/gpt-4.1-nano-2025-04-14\",\n \"error_excerpt\": \"fak: fak arm turn 1: planner: HTTP 402: {\\\"error\\\":{\\\"code\\\":\\\"no_payment_method\\\",\\\"message\\\":\\\"No payment method\\\"}}\",\n \"transcript_sha\": null\n },\n \"next_evidence_needed\": \"Attach billing/payment method for the account behind GLAMA_API_KEY.\"\n },\n {\n \"target\": \"pollinations_no_key\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://gen.pollinations.ai/v1\",\n \"model_hint\": \"openai-fast\",\n \"api_key_env\": \"POLLINATIONS_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_EXTERNAL_BLOCKER\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"BLOCKED_AUTH\",\n \"queue_state\": \"BLOCKED_EXTERNAL_STATE\",\n \"operator_prerequisite\": \"Configure a valid bearer token or access path via POLLINATIONS_API_KEY.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'pollinations_no_key|https://gen.pollinations.ai/v1|POLLINATIONS_API_KEY|openai-fast' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'pollinations_no_key|openai-compatible|https://gen.pollinations.ai/v1|POLLINATIONS_API_KEY|openai-fast' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-pollinations-no-key-retry -ApiBaseUrl 'https://gen.pollinations.ai/v1' -ApiKeyEnv POLLINATIONS_API_KEY -ApiModels 'openai-fast' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"configure_access_then_probe_and_smoke\",\n \"retry_status\": \"AUTH_REQUIRED\",\n \"latest_sweep\": {\n \"summary_path\": \"fak/experiments/agent-live/transcript-adapter-sweep-pollinations/sweep-summary.json\",\n \"status\": \"failed\",\n \"model\": \"openai-fast\",\n \"error_excerpt\": \"fak agent: env NONE_LOCAL is empty \\u2014 proceeding with no auth header (fine for a local endpoint)\\r\\nfak: fak arm turn 1: planner: HTTP 401: {\\\"success\\\":false,\\\"error\\\":{\\\"message\\\":\\\"Authentication required. Please provide an API key via Authorizati\",\n \"transcript_sha\": null\n },\n \"next_evidence_needed\": \"Configure a valid bearer token or access path via POLLINATIONS_API_KEY.\"\n },\n {\n \"target\": \"openai_api\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.openai.com/v1\",\n \"model_hint\": \"gpt-4.1-mini\",\n \"api_key_env\": \"OPENAI_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set OPENAI_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'openai_api|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'openai_api|openai-compatible|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-openai-api-retry -ApiBaseUrl 'https://api.openai.com/v1' -ApiKeyEnv OPENAI_API_KEY -ApiModels 'gpt-4.1-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set OPENAI_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"xai_api\",\n \"provider\": \"xai\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.x.ai/v1\",\n \"model_hint\": \"grok-3-mini\",\n \"api_key_env\": \"XAI_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set XAI_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'xai_api|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'xai_api|xai|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-xai-api-retry -ApiBaseUrl 'https://api.x.ai/v1' -ApiKeyEnv XAI_API_KEY -ApiModels 'grok-3-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set XAI_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"openrouter_gateway\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://openrouter.ai/api/v1\",\n \"model_hint\": \"openai/gpt-4.1-mini\",\n \"api_key_env\": \"OPENROUTER_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set OPENROUTER_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'openrouter_gateway|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'openrouter_gateway|openai-compatible|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-openrouter-gateway-retry -ApiBaseUrl 'https://openrouter.ai/api/v1' -ApiKeyEnv OPENROUTER_API_KEY -ApiModels 'openai/gpt-4.1-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set OPENROUTER_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"groq_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.groq.com/openai/v1\",\n \"model_hint\": \"llama-3.3-70b-versatile\",\n \"api_key_env\": \"GROQ_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set GROQ_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'groq_openai|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'groq_openai|openai-compatible|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-groq-openai-retry -ApiBaseUrl 'https://api.groq.com/openai/v1' -ApiKeyEnv GROQ_API_KEY -ApiModels 'llama-3.3-70b-versatile' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set GROQ_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"together_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.together.xyz/v1\",\n \"model_hint\": \"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\n \"api_key_env\": \"TOGETHER_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set TOGETHER_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'together_openai|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'together_openai|openai-compatible|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-together-openai-retry -ApiBaseUrl 'https://api.together.xyz/v1' -ApiKeyEnv TOGETHER_API_KEY -ApiModels 'meta-llama/Llama-3.3-70B-Instruct-Turbo' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set TOGETHER_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"mistral_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.mistral.ai/v1\",\n \"model_hint\": \"mistral-small-latest\",\n \"api_key_env\": \"MISTRAL_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set MISTRAL_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'mistral_openai|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'mistral_openai|openai-compatible|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-mistral-openai-retry -ApiBaseUrl 'https://api.mistral.ai/v1' -ApiKeyEnv MISTRAL_API_KEY -ApiModels 'mistral-small-latest' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set MISTRAL_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"deepseek_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.deepseek.com\",\n \"model_hint\": \"deepseek-chat\",\n \"api_key_env\": \"DEEPSEEK_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set DEEPSEEK_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'deepseek_openai|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'deepseek_openai|openai-compatible|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-deepseek-openai-retry -ApiBaseUrl 'https://api.deepseek.com' -ApiKeyEnv DEEPSEEK_API_KEY -ApiModels 'deepseek-chat' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set DEEPSEEK_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"fireworks_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.fireworks.ai/inference/v1\",\n \"model_hint\": \"accounts/fireworks/models/llama-v3p1-8b-instruct\",\n \"api_key_env\": \"FIREWORKS_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set FIREWORKS_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'fireworks_openai|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'fireworks_openai|openai-compatible|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-fireworks-openai-retry -ApiBaseUrl 'https://api.fireworks.ai/inference/v1' -ApiKeyEnv FIREWORKS_API_KEY -ApiModels 'accounts/fireworks/models/llama-v3p1-8b-instruct' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set FIREWORKS_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"perplexity_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.perplexity.ai\",\n \"model_hint\": \"sonar\",\n \"api_key_env\": \"PERPLEXITY_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set PERPLEXITY_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'perplexity_openai|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'perplexity_openai|openai-compatible|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-perplexity-openai-retry -ApiBaseUrl 'https://api.perplexity.ai' -ApiKeyEnv PERPLEXITY_API_KEY -ApiModels 'sonar' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set PERPLEXITY_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"cerebras_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.cerebras.ai/v1\",\n \"model_hint\": \"llama3.1-8b\",\n \"api_key_env\": \"CEREBRAS_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set CEREBRAS_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'cerebras_openai|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'cerebras_openai|openai-compatible|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-cerebras-openai-retry -ApiBaseUrl 'https://api.cerebras.ai/v1' -ApiKeyEnv CEREBRAS_API_KEY -ApiModels 'llama3.1-8b' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set CEREBRAS_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n }\n ],\n \"artifact_errors\": {},\n \"artifacts\": {\n \"qualification\": \"fak/experiments/api-host-bridge/api-host-qualification.json\",\n \"retry\": \"fak/experiments/api-host-bridge/api-host-retry-packet.json\"\n },\n \"qualification_summary\": {\n \"targets\": 13,\n \"in_contract_targets\": 13,\n \"live_confirmed\": 1,\n \"ready_for_live_smoke\": 0,\n \"external_blocked\": 2,\n \"needs_credential\": 10,\n \"needs_probe\": 0,\n \"out_of_contract\": 0,\n \"invalid_targets\": 0,\n \"unclassified\": 0,\n \"artifact_errors\": 0,\n \"qualification_gate\": true\n },\n \"retry_summary\": {\n \"targets\": 13,\n \"actionable_blockers\": 13,\n \"ready_for_live_bridge_run\": 0,\n \"live_bridge_confirmed\": 0,\n \"unsupported_wire\": 0,\n \"invalid_targets\": 0,\n \"shape_mismatch\": 0,\n \"unclassified\": 0,\n \"action_gaps\": 0,\n \"artifact_errors\": 0,\n \"commands\": 37,\n \"retry_packet_gate\": true\n }\n}\n"}, "13f5f1a6e0391eb28bcbea759890fb009251e5d1": {"t": 1786067339.261919, "n": 22871, "h": "889a636b61537247", "c": "{\n \"schema\": \"fak.api-host-live-smoke-queue.v1\",\n \"generated_at\": \"2026-06-18T13:12:34.948345Z\",\n \"scope_note\": \"Credential-conditioned queue for completing live API-host smoke coverage. It lists executable commands and external prerequisites; it does not run paid/keyed hosts or mark external billing/auth/access state as solved.\",\n \"summary\": {\n \"targets\": 13,\n \"complete\": 1,\n \"ready_to_execute\": 0,\n \"blocked_external_state\": 2,\n \"waiting_for_credential\": 10,\n \"ready_for_probe\": 0,\n \"unqualified\": 0,\n \"unclassified\": 0,\n \"command_gaps\": 0,\n \"commands\": 34,\n \"artifact_errors\": 0,\n \"live_smoke_queue_gate\": true\n },\n \"queue\": [\n {\n \"target\": \"gemini_openai_compatible\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://generativelanguage.googleapis.com/v1beta/openai\",\n \"model_hint\": \"gemini-2.5-flash\",\n \"api_key_env\": \"GEMINI_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_LIVE_CONFIRMED\",\n \"evidence_state\": \"LIVE_CONFIRMED\",\n \"external_state_status\": \"LIVE_CONFIRMED\",\n \"queue_state\": \"COMPLETE\",\n \"operator_prerequisite\": \"none\",\n \"commands\": [],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Committed evidence already confirms a live bridge run for this host/base URL.\"\n },\n {\n \"target\": \"glama_gateway\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://gateway.glama.ai/v1\",\n \"model_hint\": \"openai/gpt-4.1-nano-2025-04-14\",\n \"api_key_env\": \"GLAMA_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_EXTERNAL_BLOCKER\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"BLOCKED_BILLING\",\n \"queue_state\": \"BLOCKED_EXTERNAL_STATE\",\n \"operator_prerequisite\": \"Attach billing/payment method for the account behind GLAMA_API_KEY.\",\n \"commands\": [\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-glama-gateway-retry -ApiBaseUrl 'https://gateway.glama.ai/v1' -ApiKeyEnv GLAMA_API_KEY -ApiModels 'openai/gpt-4.1-nano-2025-04-14' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"fix_billing_then_smoke\",\n \"retry_status\": \"BILLING_REQUIRED\",\n \"latest_sweep\": {\n \"summary_path\": \"fak/experiments/agent-live/transcript-adapter-sweep-glama-live-smoke/sweep-summary.json\",\n \"status\": \"failed\",\n \"model\": \"openai/gpt-4.1-nano-2025-04-14\",\n \"error_excerpt\": \"fak: fak arm turn 1: planner: HTTP 402: {\\\"error\\\":{\\\"code\\\":\\\"no_payment_method\\\",\\\"message\\\":\\\"No payment method\\\"}}\",\n \"transcript_sha\": null\n },\n \"next_evidence_needed\": \"Attach billing/payment method for the account behind GLAMA_API_KEY.\"\n },\n {\n \"target\": \"pollinations_no_key\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://gen.pollinations.ai/v1\",\n \"model_hint\": \"openai-fast\",\n \"api_key_env\": \"POLLINATIONS_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_EXTERNAL_BLOCKER\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"BLOCKED_AUTH\",\n \"queue_state\": \"BLOCKED_EXTERNAL_STATE\",\n \"operator_prerequisite\": \"Configure a valid bearer token or access path via POLLINATIONS_API_KEY.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'pollinations_no_key|https://gen.pollinations.ai/v1|POLLINATIONS_API_KEY|openai-fast' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'pollinations_no_key|openai-compatible|https://gen.pollinations.ai/v1|POLLINATIONS_API_KEY|openai-fast' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-pollinations-no-key-retry -ApiBaseUrl 'https://gen.pollinations.ai/v1' -ApiKeyEnv POLLINATIONS_API_KEY -ApiModels 'openai-fast' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"configure_access_then_probe_and_smoke\",\n \"retry_status\": \"AUTH_REQUIRED\",\n \"latest_sweep\": {\n \"summary_path\": \"fak/experiments/agent-live/transcript-adapter-sweep-pollinations/sweep-summary.json\",\n \"status\": \"failed\",\n \"model\": \"openai-fast\",\n \"error_excerpt\": \"fak agent: env NONE_LOCAL is empty \\u2014 proceeding with no auth header (fine for a local endpoint)\\r\\nfak: fak arm turn 1: planner: HTTP 401: {\\\"success\\\":false,\\\"error\\\":{\\\"message\\\":\\\"Authentication required. Please provide an API key via Authorizati\",\n \"transcript_sha\": null\n },\n \"next_evidence_needed\": \"Configure a valid bearer token or access path via POLLINATIONS_API_KEY.\"\n },\n {\n \"target\": \"openai_api\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.openai.com/v1\",\n \"model_hint\": \"gpt-4.1-mini\",\n \"api_key_env\": \"OPENAI_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set OPENAI_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'openai_api|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'openai_api|openai-compatible|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-openai-api-retry -ApiBaseUrl 'https://api.openai.com/v1' -ApiKeyEnv OPENAI_API_KEY -ApiModels 'gpt-4.1-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set OPENAI_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"xai_api\",\n \"provider\": \"xai\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.x.ai/v1\",\n \"model_hint\": \"grok-3-mini\",\n \"api_key_env\": \"XAI_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set XAI_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'xai_api|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'xai_api|xai|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-xai-api-retry -ApiBaseUrl 'https://api.x.ai/v1' -ApiKeyEnv XAI_API_KEY -ApiModels 'grok-3-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set XAI_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"openrouter_gateway\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://openrouter.ai/api/v1\",\n \"model_hint\": \"openai/gpt-4.1-mini\",\n \"api_key_env\": \"OPENROUTER_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set OPENROUTER_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'openrouter_gateway|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'openrouter_gateway|openai-compatible|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-openrouter-gateway-retry -ApiBaseUrl 'https://openrouter.ai/api/v1' -ApiKeyEnv OPENROUTER_API_KEY -ApiModels 'openai/gpt-4.1-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set OPENROUTER_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"groq_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.groq.com/openai/v1\",\n \"model_hint\": \"llama-3.3-70b-versatile\",\n \"api_key_env\": \"GROQ_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set GROQ_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'groq_openai|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'groq_openai|openai-compatible|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-groq-openai-retry -ApiBaseUrl 'https://api.groq.com/openai/v1' -ApiKeyEnv GROQ_API_KEY -ApiModels 'llama-3.3-70b-versatile' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set GROQ_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"together_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.together.xyz/v1\",\n \"model_hint\": \"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\n \"api_key_env\": \"TOGETHER_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set TOGETHER_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'together_openai|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'together_openai|openai-compatible|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-together-openai-retry -ApiBaseUrl 'https://api.together.xyz/v1' -ApiKeyEnv TOGETHER_API_KEY -ApiModels 'meta-llama/Llama-3.3-70B-Instruct-Turbo' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set TOGETHER_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"mistral_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.mistral.ai/v1\",\n \"model_hint\": \"mistral-small-latest\",\n \"api_key_env\": \"MISTRAL_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set MISTRAL_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'mistral_openai|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'mistral_openai|openai-compatible|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-mistral-openai-retry -ApiBaseUrl 'https://api.mistral.ai/v1' -ApiKeyEnv MISTRAL_API_KEY -ApiModels 'mistral-small-latest' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set MISTRAL_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"deepseek_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.deepseek.com\",\n \"model_hint\": \"deepseek-chat\",\n \"api_key_env\": \"DEEPSEEK_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set DEEPSEEK_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'deepseek_openai|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'deepseek_openai|openai-compatible|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-deepseek-openai-retry -ApiBaseUrl 'https://api.deepseek.com' -ApiKeyEnv DEEPSEEK_API_KEY -ApiModels 'deepseek-chat' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set DEEPSEEK_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"fireworks_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.fireworks.ai/inference/v1\",\n \"model_hint\": \"accounts/fireworks/models/llama-v3p1-8b-instruct\",\n \"api_key_env\": \"FIREWORKS_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set FIREWORKS_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'fireworks_openai|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'fireworks_openai|openai-compatible|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-fireworks-openai-retry -ApiBaseUrl 'https://api.fireworks.ai/inference/v1' -ApiKeyEnv FIREWORKS_API_KEY -ApiModels 'accounts/fireworks/models/llama-v3p1-8b-instruct' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set FIREWORKS_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"perplexity_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.perplexity.ai\",\n \"model_hint\": \"sonar\",\n \"api_key_env\": \"PERPLEXITY_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set PERPLEXITY_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'perplexity_openai|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'perplexity_openai|openai-compatible|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-perplexity-openai-retry -ApiBaseUrl 'https://api.perplexity.ai' -ApiKeyEnv PERPLEXITY_API_KEY -ApiModels 'sonar' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set PERPLEXITY_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"cerebras_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.cerebras.ai/v1\",\n \"model_hint\": \"llama3.1-8b\",\n \"api_key_env\": \"CEREBRAS_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set CEREBRAS_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'cerebras_openai|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'cerebras_openai|openai-compatible|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-cerebras-openai-retry -ApiBaseUrl 'https://api.cerebras.ai/v1' -ApiKeyEnv CEREBRAS_API_KEY -ApiModels 'llama3.1-8b' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set CEREBRAS_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n }\n ],\n \"artifact_errors\": {},\n \"artifacts\": {\n \"qualification\": \"fak/experiments/api-host-bridge/api-host-qualification.json\",\n \"retry\": \"fak/experiments/api-host-bridge/api-host-retry-packet.json\"\n },\n \"qualification_summary\": {\n \"targets\": 13,\n \"in_contract_targets\": 13,\n \"live_confirmed\": 1,\n \"ready_for_live_smoke\": 0,\n \"external_blocked\": 2,\n \"needs_credential\": 10,\n \"needs_probe\": 0,\n \"out_of_contract\": 0,\n \"invalid_targets\": 0,\n \"unclassified\": 0,\n \"artifact_errors\": 0,\n \"qualification_gate\": true\n },\n \"retry_summary\": {\n \"targets\": 13,\n \"actionable_blockers\": 13,\n \"ready_for_live_bridge_run\": 0,\n \"live_bridge_confirmed\": 0,\n \"unsupported_wire\": 0,\n \"invalid_targets\": 0,\n \"shape_mismatch\": 0,\n \"unclassified\": 0,\n \"action_gaps\": 0,\n \"artifact_errors\": 0,\n \"commands\": 37,\n \"retry_packet_gate\": true\n }\n}\n"}, "394f65f0a4c27e2205fc5e26a67a720b18fdba5a": {"t": 1786068229.3436334, "n": 22871, "h": "889a636b61537247", "c": "{\n \"schema\": \"fak.api-host-live-smoke-queue.v1\",\n \"generated_at\": \"2026-06-18T13:12:34.948345Z\",\n \"scope_note\": \"Credential-conditioned queue for completing live API-host smoke coverage. It lists executable commands and external prerequisites; it does not run paid/keyed hosts or mark external billing/auth/access state as solved.\",\n \"summary\": {\n \"targets\": 13,\n \"complete\": 1,\n \"ready_to_execute\": 0,\n \"blocked_external_state\": 2,\n \"waiting_for_credential\": 10,\n \"ready_for_probe\": 0,\n \"unqualified\": 0,\n \"unclassified\": 0,\n \"command_gaps\": 0,\n \"commands\": 34,\n \"artifact_errors\": 0,\n \"live_smoke_queue_gate\": true\n },\n \"queue\": [\n {\n \"target\": \"gemini_openai_compatible\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://generativelanguage.googleapis.com/v1beta/openai\",\n \"model_hint\": \"gemini-2.5-flash\",\n \"api_key_env\": \"GEMINI_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_LIVE_CONFIRMED\",\n \"evidence_state\": \"LIVE_CONFIRMED\",\n \"external_state_status\": \"LIVE_CONFIRMED\",\n \"queue_state\": \"COMPLETE\",\n \"operator_prerequisite\": \"none\",\n \"commands\": [],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Committed evidence already confirms a live bridge run for this host/base URL.\"\n },\n {\n \"target\": \"glama_gateway\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://gateway.glama.ai/v1\",\n \"model_hint\": \"openai/gpt-4.1-nano-2025-04-14\",\n \"api_key_env\": \"GLAMA_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_EXTERNAL_BLOCKER\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"BLOCKED_BILLING\",\n \"queue_state\": \"BLOCKED_EXTERNAL_STATE\",\n \"operator_prerequisite\": \"Attach billing/payment method for the account behind GLAMA_API_KEY.\",\n \"commands\": [\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-glama-gateway-retry -ApiBaseUrl 'https://gateway.glama.ai/v1' -ApiKeyEnv GLAMA_API_KEY -ApiModels 'openai/gpt-4.1-nano-2025-04-14' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"fix_billing_then_smoke\",\n \"retry_status\": \"BILLING_REQUIRED\",\n \"latest_sweep\": {\n \"summary_path\": \"fak/experiments/agent-live/transcript-adapter-sweep-glama-live-smoke/sweep-summary.json\",\n \"status\": \"failed\",\n \"model\": \"openai/gpt-4.1-nano-2025-04-14\",\n \"error_excerpt\": \"fak: fak arm turn 1: planner: HTTP 402: {\\\"error\\\":{\\\"code\\\":\\\"no_payment_method\\\",\\\"message\\\":\\\"No payment method\\\"}}\",\n \"transcript_sha\": null\n },\n \"next_evidence_needed\": \"Attach billing/payment method for the account behind GLAMA_API_KEY.\"\n },\n {\n \"target\": \"pollinations_no_key\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://gen.pollinations.ai/v1\",\n \"model_hint\": \"openai-fast\",\n \"api_key_env\": \"POLLINATIONS_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_EXTERNAL_BLOCKER\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"BLOCKED_AUTH\",\n \"queue_state\": \"BLOCKED_EXTERNAL_STATE\",\n \"operator_prerequisite\": \"Configure a valid bearer token or access path via POLLINATIONS_API_KEY.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'pollinations_no_key|https://gen.pollinations.ai/v1|POLLINATIONS_API_KEY|openai-fast' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'pollinations_no_key|openai-compatible|https://gen.pollinations.ai/v1|POLLINATIONS_API_KEY|openai-fast' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-pollinations-no-key-retry -ApiBaseUrl 'https://gen.pollinations.ai/v1' -ApiKeyEnv POLLINATIONS_API_KEY -ApiModels 'openai-fast' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"configure_access_then_probe_and_smoke\",\n \"retry_status\": \"AUTH_REQUIRED\",\n \"latest_sweep\": {\n \"summary_path\": \"fak/experiments/agent-live/transcript-adapter-sweep-pollinations/sweep-summary.json\",\n \"status\": \"failed\",\n \"model\": \"openai-fast\",\n \"error_excerpt\": \"fak agent: env NONE_LOCAL is empty \\u2014 proceeding with no auth header (fine for a local endpoint)\\r\\nfak: fak arm turn 1: planner: HTTP 401: {\\\"success\\\":false,\\\"error\\\":{\\\"message\\\":\\\"Authentication required. Please provide an API key via Authorizati\",\n \"transcript_sha\": null\n },\n \"next_evidence_needed\": \"Configure a valid bearer token or access path via POLLINATIONS_API_KEY.\"\n },\n {\n \"target\": \"openai_api\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.openai.com/v1\",\n \"model_hint\": \"gpt-4.1-mini\",\n \"api_key_env\": \"OPENAI_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set OPENAI_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'openai_api|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'openai_api|openai-compatible|https://api.openai.com/v1|OPENAI_API_KEY|gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-openai-api-retry -ApiBaseUrl 'https://api.openai.com/v1' -ApiKeyEnv OPENAI_API_KEY -ApiModels 'gpt-4.1-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set OPENAI_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"xai_api\",\n \"provider\": \"xai\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.x.ai/v1\",\n \"model_hint\": \"grok-3-mini\",\n \"api_key_env\": \"XAI_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set XAI_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'xai_api|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'xai_api|xai|https://api.x.ai/v1|XAI_API_KEY|grok-3-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-xai-api-retry -ApiBaseUrl 'https://api.x.ai/v1' -ApiKeyEnv XAI_API_KEY -ApiModels 'grok-3-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set XAI_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"openrouter_gateway\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://openrouter.ai/api/v1\",\n \"model_hint\": \"openai/gpt-4.1-mini\",\n \"api_key_env\": \"OPENROUTER_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set OPENROUTER_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'openrouter_gateway|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'openrouter_gateway|openai-compatible|https://openrouter.ai/api/v1|OPENROUTER_API_KEY|openai/gpt-4.1-mini' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-openrouter-gateway-retry -ApiBaseUrl 'https://openrouter.ai/api/v1' -ApiKeyEnv OPENROUTER_API_KEY -ApiModels 'openai/gpt-4.1-mini' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set OPENROUTER_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"groq_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.groq.com/openai/v1\",\n \"model_hint\": \"llama-3.3-70b-versatile\",\n \"api_key_env\": \"GROQ_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set GROQ_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'groq_openai|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'groq_openai|openai-compatible|https://api.groq.com/openai/v1|GROQ_API_KEY|llama-3.3-70b-versatile' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-groq-openai-retry -ApiBaseUrl 'https://api.groq.com/openai/v1' -ApiKeyEnv GROQ_API_KEY -ApiModels 'llama-3.3-70b-versatile' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set GROQ_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"together_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.together.xyz/v1\",\n \"model_hint\": \"meta-llama/Llama-3.3-70B-Instruct-Turbo\",\n \"api_key_env\": \"TOGETHER_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set TOGETHER_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'together_openai|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'together_openai|openai-compatible|https://api.together.xyz/v1|TOGETHER_API_KEY|meta-llama/Llama-3.3-70B-Instruct-Turbo' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-together-openai-retry -ApiBaseUrl 'https://api.together.xyz/v1' -ApiKeyEnv TOGETHER_API_KEY -ApiModels 'meta-llama/Llama-3.3-70B-Instruct-Turbo' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set TOGETHER_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"mistral_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.mistral.ai/v1\",\n \"model_hint\": \"mistral-small-latest\",\n \"api_key_env\": \"MISTRAL_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set MISTRAL_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'mistral_openai|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'mistral_openai|openai-compatible|https://api.mistral.ai/v1|MISTRAL_API_KEY|mistral-small-latest' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-mistral-openai-retry -ApiBaseUrl 'https://api.mistral.ai/v1' -ApiKeyEnv MISTRAL_API_KEY -ApiModels 'mistral-small-latest' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set MISTRAL_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"deepseek_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.deepseek.com\",\n \"model_hint\": \"deepseek-chat\",\n \"api_key_env\": \"DEEPSEEK_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set DEEPSEEK_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'deepseek_openai|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'deepseek_openai|openai-compatible|https://api.deepseek.com|DEEPSEEK_API_KEY|deepseek-chat' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-deepseek-openai-retry -ApiBaseUrl 'https://api.deepseek.com' -ApiKeyEnv DEEPSEEK_API_KEY -ApiModels 'deepseek-chat' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set DEEPSEEK_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"fireworks_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.fireworks.ai/inference/v1\",\n \"model_hint\": \"accounts/fireworks/models/llama-v3p1-8b-instruct\",\n \"api_key_env\": \"FIREWORKS_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set FIREWORKS_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'fireworks_openai|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'fireworks_openai|openai-compatible|https://api.fireworks.ai/inference/v1|FIREWORKS_API_KEY|accounts/fireworks/models/llama-v3p1-8b-instruct' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-fireworks-openai-retry -ApiBaseUrl 'https://api.fireworks.ai/inference/v1' -ApiKeyEnv FIREWORKS_API_KEY -ApiModels 'accounts/fireworks/models/llama-v3p1-8b-instruct' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set FIREWORKS_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"perplexity_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.perplexity.ai\",\n \"model_hint\": \"sonar\",\n \"api_key_env\": \"PERPLEXITY_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set PERPLEXITY_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'perplexity_openai|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'perplexity_openai|openai-compatible|https://api.perplexity.ai|PERPLEXITY_API_KEY|sonar' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-perplexity-openai-retry -ApiBaseUrl 'https://api.perplexity.ai' -ApiKeyEnv PERPLEXITY_API_KEY -ApiModels 'sonar' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set PERPLEXITY_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n },\n {\n \"target\": \"cerebras_openai\",\n \"provider\": \"openai-compatible\",\n \"contract_class\": \"openai_compatible_upstream\",\n \"base_url\": \"https://api.cerebras.ai/v1\",\n \"model_hint\": \"llama3.1-8b\",\n \"api_key_env\": \"CEREBRAS_API_KEY\",\n \"qualification_status\": \"IN_CONTRACT_NEEDS_CREDENTIAL\",\n \"evidence_state\": \"NEEDS_OPERATOR_STATE\",\n \"external_state_status\": \"NEEDS_CREDENTIAL\",\n \"queue_state\": \"WAITING_FOR_CREDENTIAL\",\n \"operator_prerequisite\": \"Set CEREBRAS_API_KEY for this host.\",\n \"commands\": [\n \"python tools/api_host_readiness_probe.py --target 'cerebras_openai|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-readiness.json --markdown fak/experiments/api-host-bridge/api-host-readiness.md\",\n \"python tools/api_host_acceptance_probe.py --target 'cerebras_openai|openai-compatible|https://api.cerebras.ai/v1|CEREBRAS_API_KEY|llama3.1-8b' --out fak/experiments/api-host-bridge/api-host-acceptance.json --markdown fak/experiments/api-host-bridge/api-host-acceptance.md\",\n \"pwsh tools/run_transcript_adapter_sweep.ps1 -OutDir fak/experiments/agent-live/transcript-adapter-sweep-cerebras-openai-retry -ApiBaseUrl 'https://api.cerebras.ai/v1' -ApiKeyEnv CEREBRAS_API_KEY -ApiModels 'llama3.1-8b' -SkipOffline -SkipLocalShim -SkipMicrobench -MaxTurns 12 -Trials 1\"\n ],\n \"retry_action_type\": \"set_auth_env_then_probe_and_smoke\",\n \"retry_status\": \"NEEDS_AUTH_ENV\",\n \"latest_sweep\": {},\n \"next_evidence_needed\": \"Set CEREBRAS_API_KEY, then rerun readiness, acceptance, and live smoke.\"\n }\n ],\n \"artifact_errors\": {},\n \"artifacts\": {\n \"qualification\": \"fak/experiments/api-host-bridge/api-host-qualification.json\",\n \"retry\": \"fak/experiments/api-host-bridge/api-host-retry-packet.json\"\n },\n \"qualification_summary\": {\n \"targets\": 13,\n \"in_contract_targets\": 13,\n \"live_confirmed\": 1,\n \"ready_for_live_smoke\": 0,\n \"external_blocked\": 2,\n \"needs_credential\": 10,\n \"needs_probe\": 0,\n \"out_of_contract\": 0,\n \"invalid_targets\": 0,\n \"unclassified\": 0,\n \"artifact_errors\": 0,\n \"qualification_gate\": true\n },\n \"retry_summary\": {\n \"targets\": 13,\n \"actionable_blockers\": 13,\n \"ready_for_live_bridge_run\": 0,\n \"live_bridge_confirmed\": 0,\n \"unsupported_wire\": 0,\n \"invalid_targets\": 0,\n \"shape_mismatch\": 0,\n \"unclassified\": 0,\n \"action_gaps\": 0,\n \"artifact_errors\": 0,\n \"commands\": 37,\n \"retry_packet_gate\": true\n }\n}\n"}}, "cmd/fak/deepseekbench.go": {"f1c36fd45ecfb60dc33288bccf3477b30bbd53cf": {"t": 1785745621.502742, "n": 5337, "h": "39472957936720f5", "c": "package main\n\n// deepseekbench.go — `fak deepseekbench`, the thin WIRE over internal/deepseekbench:\n// the DeepSeek V4 Pro/Flash TTFT/TPOT/context-scaling SCORECARD (#3014, program\n// #3006; complements the self-host runbook #3013).\n//\n// The schema, dry-run fixture, speedup-refusal gate, and live measurement all live\n// in the pure, isolation-testable internal/deepseekbench package. This file is only\n// the flag/I-O shim: default is a NO-KEY DRY-RUN FIXTURE (JSONL rows → stdout,\n// coverage + honesty → stderr); a live run is opt-in and doubly gated behind\n// DEEPSEEK_API_KEY + an explicit --spend acknowledgement.\n//\n//\tfak deepseekbench # no-key dry-run fixture → JSONL\n//\tfak deepseekbench --out rows.jsonl # write the JSONL rows to a file\n//\tDEEPSEEK_API_KEY=… fak deepseekbench --live --spend --model deepseek-v4-pro\n//\tDEEPSEEK_API_KEY=… fak deepseekbench --live --spend \\\n//\t --base-url http://host:8000/v1 --model deepseek-ai/DeepSeek-V4-Pro # self-hosted arm\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/anthony-chaudhary/fak/internal/deepseekbench\"\n)\n\nfunc cmdDeepSeekBench(argv []string) { os.Exit(runDeepSeekBench(os.Stdout, os.Stderr, argv)) }\n\n// runDeepSeekBench is the testable core (exit code, no os.Exit): 0 ok, 1 a run error,\n// 2 a usage/gating error.\nfunc runDeepSeekBench(stdout, stderr io.Writer, argv []string) int {\n\tfs := flag.NewFlagSet(\"deepseekbench\", flag.ContinueOnError)\n\tfs.SetOutput(stderr)\n\tlive := fs.Bool(\"live\", false, \"opt-in LIVE run (requires DEEPSEEK_API_KEY and --spend); default is a no-key dry-run fixture\")\n\tspend := fs.Bool(\"spend\", false, \"explicit acknowledgement that a --live run costs money\")\n\tbaseURL := fs.String(\"base-url\", \"https://api.deepseek.com\", \"OpenAI-compatible root for a --live run (point at a self-hosted vLLM/SGLang endpoint for the self-hosted arm)\")\n\tmodel := fs.String(\"model\", deepseekbench.ModelV4Flash, \"model id to route for a --live run\")\n\toutPath := fs.String(\"out\", \"\", \"write the JSONL rows to this file (default: stdout)\")\n\tif rc, ok := parseFlagsOrHelp(fs, argv); !ok {\n\t\treturn rc\n\t}\n\n\tvar rows []deepseekbench.Row\n\tif *live {\n\t\tkey := strings.TrimSpace(os.Getenv(\"DEEPSEEK_API_KEY\"))\n\t\tif msg, ok := deepseekbench.LiveGate(key != \"\", *spend); !ok {\n\t\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", msg)\n\t\t\treturn 2\n\t\t}\n\t\trow, err := deepseekbench.MeasureStreamed(nil, *baseURL, key, *model)\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(stderr, \"fak deepseekbench: live run failed:\", err)\n\t\t\treturn 1\n\t\t}\n\t\trows = []deepseekbench.Row{row}\n\t} else {\n\t\trows = deepseekbench.DryRunRows()\n\t}\n\n\tsink := stdout\n\tif *outPath != \"\" {\n\t\tf, err := os.Create(*outPath)\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", err)\n\t\t\treturn 1\n\t\t}\n\t\tdefer f.Close()\n\t\tsink = f\n\t}\n\tif err := writeDeepSeekJSONL(sink, rows); err != nil {\n\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", err)\n\t\treturn 1\n\t}\n\n\tprintDeepSeekSummary(stderr, rows, *live)\n\treturn 0\n}\n\n// writeDeepSeekJSONL emits one compact JSON object per line — the locked scorecard format.\nfunc writeDeepSeekJSONL(w io.Writer, rows []deepseekbench.Row) error {\n\tbw := bufio.NewWriter(w)\n\tenc := json.NewEncoder(bw)\n\tfor _, r := range rows {\n\t\tif err := enc.Encode(r); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn bw.Flush()\n}\n\n// printDeepSeekSummary writes the human-facing coverage + honesty summary to stderr\n// (the JSONL rows themselves are the machine artifact on stdout/--out).\nfunc printDeepSeekSummary(w io.Writer, rows []deepseekbench.Row, live bool) {\n\tmodels := map[string]int{}\n\tfor _, r := range rows {\n\t\tmodels[r.ModelID]++\n\t}\n\tids := make([]string, 0, len(models))\n\tfor id := range models {\n\t\tids = append(ids, id)\n\t}\n\tsort.Strings(ids)\n\tfmt.Fprintln(w, \"== fak deepseekbench — DeepSeek V4 Pro/Flash TTFT/TPOT/context scorecard (#3014) ==\")\n\tif live {\n\t\tfmt.Fprintln(w, \"mode: LIVE (measurement=\\\"live\\\"; latency is provider-observed)\")\n\t} else {\n\t\tfmt.Fprintln(w, \"mode: DRY-RUN FIXTURE (measurement=\\\"dry-run-fixture\\\"; every latency is a labelled placeholder, NOT measured)\")\n\t}\n\tfmt.Fprintf(w, \"rows: %d across models:\\n\", len(rows))\n\tfor _, id := range ids {\n\t\tfmt.Fprintf(w, \" %-22s %d rows\\n\", id, models[id])\n\t}\n\t// Demonstrate the speedup-refusal gate on a DeepSeek row vs a non-DeepSeek baseline.\n\tfmt.Fprintf(w, \"scorecard speedup gate: %s\\n\", deepSeekGateExample(rows))\n\tfmt.Fprintln(w, \"OBSERVED provider speed is never a fak-authored saving — see docs/benchmarks/DEEPSEEK-V4-PERF-SCORECARD.md\")\n\tif !live {\n\t\tfmt.Fprintln(w, \"live recipe: DEEPSEEK_API_KEY=… fak deepseekbench --live --spend --model deepseek-v4-pro\")\n\t}\n}\n\n// deepSeekGateExample runs the refusal gate over the first DeepSeek row and the first\n// non-DeepSeek baseline row, so the summary always shows what the gate decides.\nfunc deepSeekGateExample(rows []deepseekbench.Row) string {\n\tvar subject, baseline *deepseekbench.Row\n\tfor i := range rows {\n\t\tif rows[i].ProviderRoute == \"deepseek\" && subject == nil {\n\t\t\tsubject = &rows[i]\n\t\t}\n\t\tif rows[i].ProviderRoute != \"deepseek\" && baseline == nil {\n\t\t\tbaseline = &rows[i]\n\t\t}\n\t}\n\tif subject == nil || baseline == nil {\n\t\treturn \"[NOT COMPARABLE: need one DeepSeek row and one baseline row]\"\n\t}\n\tline, _ := deepseekbench.CompareSpeedup(*subject, *baseline)\n\treturn line\n}\n"}, "625d1226bafb4c145d94ae4433f0a16554170a35": {"t": 1785749565.9031885, "n": 5337, "h": "39472957936720f5", "c": "package main\n\n// deepseekbench.go — `fak deepseekbench`, the thin WIRE over internal/deepseekbench:\n// the DeepSeek V4 Pro/Flash TTFT/TPOT/context-scaling SCORECARD (#3014, program\n// #3006; complements the self-host runbook #3013).\n//\n// The schema, dry-run fixture, speedup-refusal gate, and live measurement all live\n// in the pure, isolation-testable internal/deepseekbench package. This file is only\n// the flag/I-O shim: default is a NO-KEY DRY-RUN FIXTURE (JSONL rows → stdout,\n// coverage + honesty → stderr); a live run is opt-in and doubly gated behind\n// DEEPSEEK_API_KEY + an explicit --spend acknowledgement.\n//\n//\tfak deepseekbench # no-key dry-run fixture → JSONL\n//\tfak deepseekbench --out rows.jsonl # write the JSONL rows to a file\n//\tDEEPSEEK_API_KEY=… fak deepseekbench --live --spend --model deepseek-v4-pro\n//\tDEEPSEEK_API_KEY=… fak deepseekbench --live --spend \\\n//\t --base-url http://host:8000/v1 --model deepseek-ai/DeepSeek-V4-Pro # self-hosted arm\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/anthony-chaudhary/fak/internal/deepseekbench\"\n)\n\nfunc cmdDeepSeekBench(argv []string) { os.Exit(runDeepSeekBench(os.Stdout, os.Stderr, argv)) }\n\n// runDeepSeekBench is the testable core (exit code, no os.Exit): 0 ok, 1 a run error,\n// 2 a usage/gating error.\nfunc runDeepSeekBench(stdout, stderr io.Writer, argv []string) int {\n\tfs := flag.NewFlagSet(\"deepseekbench\", flag.ContinueOnError)\n\tfs.SetOutput(stderr)\n\tlive := fs.Bool(\"live\", false, \"opt-in LIVE run (requires DEEPSEEK_API_KEY and --spend); default is a no-key dry-run fixture\")\n\tspend := fs.Bool(\"spend\", false, \"explicit acknowledgement that a --live run costs money\")\n\tbaseURL := fs.String(\"base-url\", \"https://api.deepseek.com\", \"OpenAI-compatible root for a --live run (point at a self-hosted vLLM/SGLang endpoint for the self-hosted arm)\")\n\tmodel := fs.String(\"model\", deepseekbench.ModelV4Flash, \"model id to route for a --live run\")\n\toutPath := fs.String(\"out\", \"\", \"write the JSONL rows to this file (default: stdout)\")\n\tif rc, ok := parseFlagsOrHelp(fs, argv); !ok {\n\t\treturn rc\n\t}\n\n\tvar rows []deepseekbench.Row\n\tif *live {\n\t\tkey := strings.TrimSpace(os.Getenv(\"DEEPSEEK_API_KEY\"))\n\t\tif msg, ok := deepseekbench.LiveGate(key != \"\", *spend); !ok {\n\t\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", msg)\n\t\t\treturn 2\n\t\t}\n\t\trow, err := deepseekbench.MeasureStreamed(nil, *baseURL, key, *model)\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(stderr, \"fak deepseekbench: live run failed:\", err)\n\t\t\treturn 1\n\t\t}\n\t\trows = []deepseekbench.Row{row}\n\t} else {\n\t\trows = deepseekbench.DryRunRows()\n\t}\n\n\tsink := stdout\n\tif *outPath != \"\" {\n\t\tf, err := os.Create(*outPath)\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", err)\n\t\t\treturn 1\n\t\t}\n\t\tdefer f.Close()\n\t\tsink = f\n\t}\n\tif err := writeDeepSeekJSONL(sink, rows); err != nil {\n\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", err)\n\t\treturn 1\n\t}\n\n\tprintDeepSeekSummary(stderr, rows, *live)\n\treturn 0\n}\n\n// writeDeepSeekJSONL emits one compact JSON object per line — the locked scorecard format.\nfunc writeDeepSeekJSONL(w io.Writer, rows []deepseekbench.Row) error {\n\tbw := bufio.NewWriter(w)\n\tenc := json.NewEncoder(bw)\n\tfor _, r := range rows {\n\t\tif err := enc.Encode(r); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn bw.Flush()\n}\n\n// printDeepSeekSummary writes the human-facing coverage + honesty summary to stderr\n// (the JSONL rows themselves are the machine artifact on stdout/--out).\nfunc printDeepSeekSummary(w io.Writer, rows []deepseekbench.Row, live bool) {\n\tmodels := map[string]int{}\n\tfor _, r := range rows {\n\t\tmodels[r.ModelID]++\n\t}\n\tids := make([]string, 0, len(models))\n\tfor id := range models {\n\t\tids = append(ids, id)\n\t}\n\tsort.Strings(ids)\n\tfmt.Fprintln(w, \"== fak deepseekbench — DeepSeek V4 Pro/Flash TTFT/TPOT/context scorecard (#3014) ==\")\n\tif live {\n\t\tfmt.Fprintln(w, \"mode: LIVE (measurement=\\\"live\\\"; latency is provider-observed)\")\n\t} else {\n\t\tfmt.Fprintln(w, \"mode: DRY-RUN FIXTURE (measurement=\\\"dry-run-fixture\\\"; every latency is a labelled placeholder, NOT measured)\")\n\t}\n\tfmt.Fprintf(w, \"rows: %d across models:\\n\", len(rows))\n\tfor _, id := range ids {\n\t\tfmt.Fprintf(w, \" %-22s %d rows\\n\", id, models[id])\n\t}\n\t// Demonstrate the speedup-refusal gate on a DeepSeek row vs a non-DeepSeek baseline.\n\tfmt.Fprintf(w, \"scorecard speedup gate: %s\\n\", deepSeekGateExample(rows))\n\tfmt.Fprintln(w, \"OBSERVED provider speed is never a fak-authored saving — see docs/benchmarks/DEEPSEEK-V4-PERF-SCORECARD.md\")\n\tif !live {\n\t\tfmt.Fprintln(w, \"live recipe: DEEPSEEK_API_KEY=… fak deepseekbench --live --spend --model deepseek-v4-pro\")\n\t}\n}\n\n// deepSeekGateExample runs the refusal gate over the first DeepSeek row and the first\n// non-DeepSeek baseline row, so the summary always shows what the gate decides.\nfunc deepSeekGateExample(rows []deepseekbench.Row) string {\n\tvar subject, baseline *deepseekbench.Row\n\tfor i := range rows {\n\t\tif rows[i].ProviderRoute == \"deepseek\" && subject == nil {\n\t\t\tsubject = &rows[i]\n\t\t}\n\t\tif rows[i].ProviderRoute != \"deepseek\" && baseline == nil {\n\t\t\tbaseline = &rows[i]\n\t\t}\n\t}\n\tif subject == nil || baseline == nil {\n\t\treturn \"[NOT COMPARABLE: need one DeepSeek row and one baseline row]\"\n\t}\n\tline, _ := deepseekbench.CompareSpeedup(*subject, *baseline)\n\treturn line\n}\n"}, "6486f13daf271d35a71b7694eb92ac9fc5f5754a": {"t": 1785749567.148051, "n": 21164, "h": "03fa51a45144affc", "c": "package main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n)\n\n// fak deepseekbench — the DeepSeek V4 Pro/Flash TTFT/TPOT/context-scaling\n// SCORECARD (#3014, under the DeepSeek V4 support program #3006; complements the\n// self-host wire-readiness runbook #3013).\n//\n// It emits one JSONL row per (model × context bucket × output target × reasoning\n// mode × stream/non-stream) cell with the performance axes the issue locks: TTFT,\n// TPOT, E2E, output tok/s, prompt/completion/reasoning tokens, and the provider's\n// prompt-cache hit/miss counters. V4 Pro and V4 Flash are BOTH in the same\n// scorecard — Flash behavior is never inferred from Pro.\n//\n// TWO modes, one schema:\n//\n//\tfak deepseekbench DEFAULT: a no-key DRY-RUN FIXTURE. Every latency\n//\t number is a labelled PLACEHOLDER (measurement=\n//\t \"dry-run-fixture\"), NOT a measurement — the fixture\n//\t exists to lock the row schema and prove the harness\n//\t runs keyless in CI, exactly like the #3013 smoke\n//\t that skips cleanly with no upstream.\n//\tfak deepseekbench --live --spend OPT-IN LIVE run. Refuses unless DEEPSEEK_API_KEY is\n//\t set AND --spend is passed (an explicit money ack).\n//\t Only a live row carries measurement=\"live\" and\n//\t speed_provenance=\"provider-observed\".\n//\n// HONESTY FENCE: this scorecard reports OBSERVED PROVIDER SPEED. A DeepSeek number is\n// never a fak-authored saving — the split is the same OBSERVED-vs-WITNESSED discipline\n// internal/gateway/deepseek_pricing.go applies to the cache counters. The scorecard's\n// speedup line REFUSES to print a delta unless the two rows share a prompt shape and a\n// verified quality parity and are both live measurements (see compareSpeedup).\n//\n//\tfak deepseekbench --out rows.jsonl write the JSONL rows to a file\n//\tfak deepseekbench --base-url URL --model M point the live run at a self-hosted\n//\t OpenAI-compatible endpoint (default hosted)\nfunc cmdDeepSeekBench(argv []string) { os.Exit(runDeepSeekBench(os.Stdout, os.Stderr, argv)) }\n\n// DeepSeekBenchRow is ONE scorecard cell. The JSON tags are the locked schema the\n// field-lock test (TestDeepSeekBenchRequiredFields) pins — adding/renaming a field\n// without updating requiredBenchFields fails the test on purpose.\ntype DeepSeekBenchRow struct {\n\t// Provenance / honesty — read these BEFORE any latency number.\n\tMeasurement string `json:\"measurement\"` // \"dry-run-fixture\" | \"live\"\n\tSpeedProvenance string `json:\"speed_provenance\"` // \"fixture-placeholder-not-measured\" | \"provider-observed\"\n\n\t// Route identity.\n\tModelID string `json:\"model_id\"`\n\tProviderRoute string `json:\"provider_route\"` // e.g. \"deepseek\" | \"anthropic\" | \"openai-compatible\"\n\tEngineRoute string `json:\"engine_route\"` // e.g. \"hosted-api\" | \"vllm\" | \"sglang\"\n\tHosting string `json:\"hosting\"` // \"hosted\" | \"self-hosted\"\n\n\t// Prompt / output shape.\n\tContextBucket string `json:\"context_bucket\"` // 4K | 32K | 128K | 512K | 1M\n\tOutputTarget string `json:\"output_target\"` // short | 1K | 8K | long-reasoning\n\tReasoningMode string `json:\"reasoning_mode\"` // non-thinking | high | max\n\tStream bool `json:\"stream\"`\n\n\t// Latency / throughput.\n\tTTFTMillis float64 `json:\"ttft_ms\"`\n\tTPOTMillis float64 `json:\"tpot_ms\"`\n\tE2EMillis float64 `json:\"e2e_ms\"`\n\tOutputToksPerSec float64 `json:\"output_toks_per_s\"`\n\n\t// Token counters (reasoning_tokens is 0 when the provider does not expose them).\n\tPromptTokens int `json:\"prompt_tokens\"`\n\tCompletionTokens int `json:\"completion_tokens\"`\n\tReasoningTokens int `json:\"reasoning_tokens\"`\n\n\t// Prompt-cache counters + attribution (provider-observed; never fak-authored).\n\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\tCacheAttribution string `json:\"cache_attribution\"` // \"provider-observed\" | \"unknown-dry-run\"\n\n\t// Comparability keys — the scorecard refuses a speedup unless these line up.\n\tPromptShape string `json:\"prompt_shape\"` // shape descriptor (bucket|output|reasoning|stream)\n\tQualityParity string `json:\"quality_parity\"` // \"unknown\" | \"verified\" | \"differs\"\n}\n\n// requiredBenchFields is the locked set of JSON keys every emitted row MUST carry.\n// The field-lock test marshals a row and asserts each key is present, so the issue's\n// required-field list can never silently drift.\nfunc requiredBenchFields() []string {\n\treturn []string{\n\t\t\"measurement\", \"speed_provenance\",\n\t\t\"model_id\", \"provider_route\", \"engine_route\", \"hosting\",\n\t\t\"context_bucket\", \"output_target\", \"reasoning_mode\", \"stream\",\n\t\t\"ttft_ms\", \"tpot_ms\", \"e2e_ms\", \"output_toks_per_s\",\n\t\t\"prompt_tokens\", \"completion_tokens\", \"reasoning_tokens\",\n\t\t\"prompt_cache_hit_tokens\", \"prompt_cache_miss_tokens\", \"cache_attribution\",\n\t\t\"prompt_shape\", \"quality_parity\",\n\t}\n}\n\n// The locked axis vocabularies (the issue's buckets/targets/modes).\nvar (\n\tbenchContextBuckets = []string{\"4K\", \"32K\", \"128K\", \"512K\", \"1M\"}\n\tbenchOutputTargets = []string{\"short\", \"1K\", \"8K\", \"long-reasoning\"}\n\tbenchReasoningModes = []string{\"non-thinking\", \"high\", \"max\"}\n)\n\n// contextBucketTokens maps a bucket label to its nominal prompt-token count.\nfunc contextBucketTokens(bucket string) int {\n\tswitch bucket {\n\tcase \"4K\":\n\t\treturn 4096\n\tcase \"32K\":\n\t\treturn 32768\n\tcase \"128K\":\n\t\treturn 131072\n\tcase \"512K\":\n\t\treturn 524288\n\tcase \"1M\":\n\t\treturn 1048576\n\t}\n\treturn 0\n}\n\n// outputTargetTokens maps an output target to its nominal completion-token count.\nfunc outputTargetTokens(target string) int {\n\tswitch target {\n\tcase \"short\":\n\t\treturn 64\n\tcase \"1K\":\n\t\treturn 1024\n\tcase \"8K\":\n\t\treturn 8192\n\tcase \"long-reasoning\":\n\t\treturn 16384\n\t}\n\treturn 0\n}\n\n// promptShape is the comparability key: two rows are comparable only when it matches.\nfunc promptShape(bucket, output, reasoning string, stream bool) string {\n\treturn fmt.Sprintf(\"%s|%s|%s|stream=%t\", bucket, output, reasoning, stream)\n}\n\n// benchModel is one model's fixture-latency parameters. These are PLACEHOLDERS used\n// only to shape the dry-run fixture; they are NOT measurements and never leave a\n// dry-run row (measurement stays \"dry-run-fixture\").\ntype benchModel struct {\n\tid string\n\tprefillMsPer float64 // fixture prefill ms per 1K prompt tokens\n\ttpotMs float64 // fixture per-output-token ms\n}\n\n// deepseekFixtureModels are V4 Pro and V4 Flash — both, always, side by side. The\n// numbers reflect only the coarse expectation that the 49B-active Pro decodes slower\n// per token than the 13B-active Flash; they are labelled placeholders, not evidence.\nfunc deepseekFixtureModels() []benchModel {\n\treturn []benchModel{\n\t\t{id: \"deepseek-v4-pro\", prefillMsPer: 0.18, tpotMs: 22},\n\t\t{id: \"deepseek-v4-flash\", prefillMsPer: 0.09, tpotMs: 9},\n\t}\n}\n\n// dryRunRows builds the deterministic no-key fixture: the full model × bucket ×\n// output × reasoning × stream matrix (skipping the contradictory non-thinking +\n// long-reasoning cells), plus one fak-route baseline row so the scorecard has a\n// same-harness comparand to (correctly) REFUSE comparing against in dry-run.\nfunc dryRunRows() []DeepSeekBenchRow {\n\tvar rows []DeepSeekBenchRow\n\tfor _, m := range deepseekFixtureModels() {\n\t\tfor _, bucket := range benchContextBuckets {\n\t\t\tfor _, output := range benchOutputTargets {\n\t\t\t\tfor _, reasoning := range benchReasoningModes {\n\t\t\t\t\tif output == \"long-reasoning\" && reasoning == \"non-thinking\" {\n\t\t\t\t\t\tcontinue // a non-thinking route does not emit a long reasoning trace\n\t\t\t\t\t}\n\t\t\t\t\tfor _, stream := range []bool{true, false} {\n\t\t\t\t\t\trows = append(rows, fixtureRow(m, bucket, output, reasoning, stream))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// One existing-fak-route baseline row, same harness, same schema. It is also a\n\t// fixture, so the scorecard MUST refuse to compare it (measurement != live).\n\trows = append(rows, DeepSeekBenchRow{\n\t\tMeasurement: \"dry-run-fixture\",\n\t\tSpeedProvenance: \"fixture-placeholder-not-measured\",\n\t\tModelID: \"claude-sonnet-5\",\n\t\tProviderRoute: \"anthropic\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: \"32K\",\n\t\tOutputTarget: \"1K\",\n\t\tReasoningMode: \"non-thinking\",\n\t\tStream: true,\n\t\tCacheAttribution: \"unknown-dry-run\",\n\t\tPromptShape: promptShape(\"32K\", \"1K\", \"non-thinking\", true),\n\t\tQualityParity: \"unknown\",\n\t\tPromptTokens: contextBucketTokens(\"32K\"),\n\t\tCompletionTokens: outputTargetTokens(\"1K\"),\n\t})\n\treturn rows\n}\n\n// fixtureRow computes one deterministic placeholder row. No time/rand: every number\n// is a pure function of the axes, so the fixture is byte-stable across runs.\nfunc fixtureRow(m benchModel, bucket, output, reasoning string, stream bool) DeepSeekBenchRow {\n\tprompt := contextBucketTokens(bucket)\n\tcompletion := outputTargetTokens(output)\n\treasoningTokens := 0\n\tswitch reasoning {\n\tcase \"high\":\n\t\treasoningTokens = completion\n\tcase \"max\":\n\t\treasoningTokens = completion * 3\n\t}\n\tgenTokens := completion + reasoningTokens\n\tttft := (float64(prompt) / 1000.0) * m.prefillMsPer\n\ttpot := m.tpotMs\n\tvar e2e, toksPerSec float64\n\tif stream {\n\t\te2e = ttft + tpot*float64(genTokens)\n\t} else {\n\t\t// Non-stream: no incremental token stream, so TPOT is not observable; the whole\n\t\t// generation lands at once and TTFT collapses into E2E.\n\t\ttpot = 0\n\t\te2e = ttft + m.tpotMs*float64(genTokens)\n\t\tttft = e2e\n\t}\n\tif e2e > 0 {\n\t\ttoksPerSec = float64(genTokens) / (e2e / 1000.0)\n\t}\n\treturn DeepSeekBenchRow{\n\t\tMeasurement: \"dry-run-fixture\",\n\t\tSpeedProvenance: \"fixture-placeholder-not-measured\",\n\t\tModelID: m.id,\n\t\tProviderRoute: \"deepseek\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: bucket,\n\t\tOutputTarget: output,\n\t\tReasoningMode: reasoning,\n\t\tStream: stream,\n\t\tTTFTMillis: round2(ttft),\n\t\tTPOTMillis: round2(tpot),\n\t\tE2EMillis: round2(e2e),\n\t\tOutputToksPerSec: round2(toksPerSec),\n\t\tPromptTokens: prompt,\n\t\tCompletionTokens: completion,\n\t\tReasoningTokens: reasoningTokens,\n\t\t// The fixture asserts nothing about cache behavior — a real hit/miss split is a\n\t\t// provider-observed number only a live run may fill.\n\t\tPromptCacheHitTokens: 0,\n\t\tPromptCacheMissTokens: 0,\n\t\tCacheAttribution: \"unknown-dry-run\",\n\t\tPromptShape: promptShape(bucket, output, reasoning, stream),\n\t\tQualityParity: \"unknown\",\n\t}\n}\n\nfunc round2(v float64) float64 {\n\treturn float64(int64(v*100+0.5)) / 100\n}\n\n// compareSpeedup is the honesty gate the issue demands: it prints a speedup delta\n// ONLY when the DeepSeek row and the baseline row (a) share a prompt shape, (b) both\n// carry a verified quality parity, and (c) are both live measurements. Any other case\n// returns a \"[NOT COMPARABLE: …]\" line and printed=false — a dry-run fixture, a shape\n// mismatch, or an unverified parity can never surface as a speed headline.\nfunc compareSpeedup(subject, baseline DeepSeekBenchRow) (line string, printed bool) {\n\tswitch {\n\tcase subject.Measurement != \"live\" || baseline.Measurement != \"live\":\n\t\treturn \"[NOT COMPARABLE: dry-run fixture — no measured latency; run --live with a parity check]\", false\n\tcase subject.PromptShape != baseline.PromptShape:\n\t\treturn fmt.Sprintf(\"[NOT COMPARABLE: prompt shape differs (%s vs %s)]\", subject.PromptShape, baseline.PromptShape), false\n\tcase subject.QualityParity != \"verified\" || baseline.QualityParity != \"verified\":\n\t\treturn \"[NOT COMPARABLE: quality parity not verified for both rows]\", false\n\tcase baseline.E2EMillis <= 0 || subject.E2EMillis <= 0:\n\t\treturn \"[NOT COMPARABLE: a measured E2E is missing]\", false\n\t}\n\tratio := baseline.E2EMillis / subject.E2EMillis\n\treturn fmt.Sprintf(\"OBSERVED provider speed: %s is %.2f× the E2E of %s at shape %s (provider-observed, not a fak-authored saving)\",\n\t\tsubject.ModelID, ratio, baseline.ModelID, subject.PromptShape), true\n}\n\n// runDeepSeekBench is the testable core (exit code, no os.Exit): 0 ok, 1 a run error,\n// 2 a usage/gating error.\nfunc runDeepSeekBench(stdout, stderr io.Writer, argv []string) int {\n\tfs := flag.NewFlagSet(\"deepseekbench\", flag.ContinueOnError)\n\tfs.SetOutput(stderr)\n\tlive := fs.Bool(\"live\", false, \"opt-in LIVE run (requires DEEPSEEK_API_KEY and --spend); default is a no-key dry-run fixture\")\n\tspend := fs.Bool(\"spend\", false, \"explicit acknowledgement that a --live run costs money\")\n\tbaseURL := fs.String(\"base-url\", \"https://api.deepseek.com\", \"OpenAI-compatible root for a --live run (point at a self-hosted vLLM/SGLang endpoint for the self-hosted arm)\")\n\tmodel := fs.String(\"model\", \"deepseek-v4-flash\", \"model id to route for a --live run\")\n\toutPath := fs.String(\"out\", \"\", \"write the JSONL rows to this file (default: stdout)\")\n\tif rc, ok := parseFlagsOrHelp(fs, argv); !ok {\n\t\treturn rc\n\t}\n\n\tvar rows []DeepSeekBenchRow\n\tif *live {\n\t\tgot, rc := liveRows(stderr, *baseURL, *model, *spend)\n\t\tif rc != 0 {\n\t\t\treturn rc\n\t\t}\n\t\trows = got\n\t} else {\n\t\trows = dryRunRows()\n\t}\n\n\tsink := stdout\n\tif *outPath != \"\" {\n\t\tf, err := os.Create(*outPath)\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", err)\n\t\t\treturn 1\n\t\t}\n\t\tdefer f.Close()\n\t\tsink = f\n\t}\n\tif err := writeBenchJSONL(sink, rows); err != nil {\n\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", err)\n\t\treturn 1\n\t}\n\n\tprintBenchSummary(stderr, rows, *live)\n\treturn 0\n}\n\n// writeBenchJSONL emits one compact JSON object per line — the locked scorecard format.\nfunc writeBenchJSONL(w io.Writer, rows []DeepSeekBenchRow) error {\n\tbw := bufio.NewWriter(w)\n\tenc := json.NewEncoder(bw)\n\tfor _, r := range rows {\n\t\tif err := enc.Encode(r); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn bw.Flush()\n}\n\n// printBenchSummary writes the human-facing coverage + honesty summary to stderr (the\n// JSONL rows themselves are the machine artifact on stdout/--out).\nfunc printBenchSummary(w io.Writer, rows []DeepSeekBenchRow, live bool) {\n\tmodels := map[string]int{}\n\tfor _, r := range rows {\n\t\tmodels[r.ModelID]++\n\t}\n\tids := make([]string, 0, len(models))\n\tfor id := range models {\n\t\tids = append(ids, id)\n\t}\n\tsort.Strings(ids)\n\tfmt.Fprintln(w, \"== fak deepseekbench — DeepSeek V4 Pro/Flash TTFT/TPOT/context scorecard (#3014) ==\")\n\tif live {\n\t\tfmt.Fprintln(w, \"mode: LIVE (measurement=\\\"live\\\"; latency is provider-observed)\")\n\t} else {\n\t\tfmt.Fprintln(w, \"mode: DRY-RUN FIXTURE (measurement=\\\"dry-run-fixture\\\"; every latency is a labelled placeholder, NOT measured)\")\n\t}\n\tfmt.Fprintf(w, \"rows: %d across models:\\n\", len(rows))\n\tfor _, id := range ids {\n\t\tfmt.Fprintf(w, \" %-22s %d rows\\n\", id, models[id])\n\t}\n\t// Demonstrate the speedup-refusal gate on the first two DeepSeek/baseline rows.\n\tline, _ := scorecardExample(rows)\n\tfmt.Fprintf(w, \"scorecard speedup gate: %s\\n\", line)\n\tfmt.Fprintln(w, \"OBSERVED provider speed is never a fak-authored saving — see docs/benchmarks/DEEPSEEK-V4-PERF-SCORECARD.md\")\n\tif !live {\n\t\tfmt.Fprintln(w, \"live recipe: DEEPSEEK_API_KEY=… fak deepseekbench --live --spend --model deepseek-v4-pro\")\n\t}\n}\n\n// scorecardExample picks a DeepSeek subject row and a non-DeepSeek baseline row (if\n// present) and runs the refusal gate over them, so the summary always shows what the\n// gate decides on the current rows.\nfunc scorecardExample(rows []DeepSeekBenchRow) (string, bool) {\n\tvar subject, baseline *DeepSeekBenchRow\n\tfor i := range rows {\n\t\tif rows[i].ProviderRoute == \"deepseek\" && subject == nil {\n\t\t\tsubject = &rows[i]\n\t\t}\n\t\tif rows[i].ProviderRoute != \"deepseek\" && baseline == nil {\n\t\t\tbaseline = &rows[i]\n\t\t}\n\t}\n\tif subject == nil || baseline == nil {\n\t\treturn \"[NOT COMPARABLE: need one DeepSeek row and one baseline row]\", false\n\t}\n\treturn compareSpeedup(*subject, *baseline)\n}\n\n// --- live measurement arm (opt-in, gated, unwitnessed in CI) --------------------\n\n// liveRows gates the live arm and, when admitted, measures one streamed request. It\n// returns (rows, exitCode); a non-zero code means the gate refused BEFORE any network.\nfunc liveRows(stderr io.Writer, baseURL, model string, spend bool) ([]DeepSeekBenchRow, int) {\n\tkey := strings.TrimSpace(os.Getenv(\"DEEPSEEK_API_KEY\"))\n\tif key == \"\" {\n\t\tfmt.Fprintln(stderr, \"fak deepseekbench: --live needs DEEPSEEK_API_KEY set (refusing before any network call)\")\n\t\treturn nil, 2\n\t}\n\tif !spend {\n\t\tfmt.Fprintln(stderr, \"fak deepseekbench: --live costs money — pass --spend to acknowledge (refusing)\")\n\t\treturn nil, 2\n\t}\n\trow, err := measureStreamed(baseURL, key, model)\n\tif err != nil {\n\t\tfmt.Fprintln(stderr, \"fak deepseekbench: live run failed:\", err)\n\t\treturn nil, 1\n\t}\n\treturn []DeepSeekBenchRow{row}, 0\n}\n\n// measureStreamed issues one streaming chat/completions request and times TTFT (first\n// content delta), TPOT (mean inter-delta), E2E, and reads the final usage block for the\n// token + prompt-cache counters. It is the untested-in-CI arm: it only runs behind the\n// key+spend gate, mirroring the #3013 self-host smoke that skips cleanly without a node.\nfunc measureStreamed(baseURL, key, model string) (DeepSeekBenchRow, error) {\n\tbody, _ := json.Marshal(map[string]any{\n\t\t\"model\": model,\n\t\t\"stream\": true,\n\t\t\"messages\": []map[string]string{{\"role\": \"user\", \"content\": \"Reply with a short sentence.\"}},\n\t\t\"stream_options\": map[string]any{\"include_usage\": true},\n\t})\n\turl := strings.TrimRight(baseURL, \"/\") + \"/chat/completions\"\n\treq, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))\n\tif err != nil {\n\t\treturn DeepSeekBenchRow{}, err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+key)\n\n\tclient := &http.Client{Timeout: 5 * time.Minute}\n\tstart := time.Now()\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn DeepSeekBenchRow{}, err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\tsnippet, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\n\t\treturn DeepSeekBenchRow{}, fmt.Errorf(\"upstream status %d: %s\", resp.StatusCode, strings.TrimSpace(string(snippet)))\n\t}\n\n\tvar ttft time.Duration\n\tvar deltas int\n\tvar lastDelta time.Time\n\tvar interSum time.Duration\n\tprompt, completion, reasoning, hit, miss := 0, 0, 0, 0, 0\n\tsc := bufio.NewScanner(resp.Body)\n\tsc.Buffer(make([]byte, 0, 64*1024), 4*1024*1024)\n\tfor sc.Scan() {\n\t\tline := strings.TrimSpace(sc.Text())\n\t\tif !strings.HasPrefix(line, \"data:\") {\n\t\t\tcontinue\n\t\t}\n\t\tdata := strings.TrimSpace(strings.TrimPrefix(line, \"data:\"))\n\t\tif data == \"[DONE]\" {\n\t\t\tbreak\n\t\t}\n\t\tvar ev struct {\n\t\t\tChoices []struct {\n\t\t\t\tDelta struct {\n\t\t\t\t\tContent string `json:\"content\"`\n\t\t\t\t\tReasoningContent string `json:\"reasoning_content\"`\n\t\t\t\t} `json:\"delta\"`\n\t\t\t} `json:\"choices\"`\n\t\t\tUsage *struct {\n\t\t\t\tPromptTokens int `json:\"prompt_tokens\"`\n\t\t\t\tCompletionTokens int `json:\"completion_tokens\"`\n\t\t\t\tReasoningTokens int `json:\"reasoning_tokens\"`\n\t\t\t\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\t\t\t\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\t\t\t} `json:\"usage\"`\n\t\t}\n\t\tif err := json.Unmarshal([]byte(data), &ev); err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif len(ev.Choices) > 0 && (ev.Choices[0].Delta.Content != \"\" || ev.Choices[0].Delta.ReasoningContent != \"\") {\n\t\t\tnow := time.Now()\n\t\t\tif ttft == 0 {\n\t\t\t\tttft = now.Sub(start)\n\t\t\t} else {\n\t\t\t\tinterSum += now.Sub(lastDelta)\n\t\t\t}\n\t\t\tlastDelta = now\n\t\t\tdeltas++\n\t\t}\n\t\tif ev.Usage != nil {\n\t\t\tprompt = ev.Usage.PromptTokens\n\t\t\tcompletion = ev.Usage.CompletionTokens\n\t\t\treasoning = ev.Usage.ReasoningTokens\n\t\t\thit = ev.Usage.PromptCacheHitTokens\n\t\t\tmiss = ev.Usage.PromptCacheMissTokens\n\t\t}\n\t}\n\tif err := sc.Err(); err != nil {\n\t\treturn DeepSeekBenchRow{}, err\n\t}\n\te2e := time.Since(start)\n\ttpot := 0.0\n\tif deltas > 1 {\n\t\ttpot = float64(interSum.Milliseconds()) / float64(deltas-1)\n\t}\n\ttoksPerSec := 0.0\n\tif e2e > 0 && completion > 0 {\n\t\ttoksPerSec = float64(completion) / e2e.Seconds()\n\t}\n\tattribution := \"unknown\"\n\tif hit > 0 || miss > 0 {\n\t\tattribution = \"provider-observed\"\n\t}\n\treturn DeepSeekBenchRow{\n\t\tMeasurement: \"live\",\n\t\tSpeedProvenance: \"provider-observed\",\n\t\tModelID: model,\n\t\tProviderRoute: \"deepseek\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: \"4K\",\n\t\tOutputTarget: \"short\",\n\t\tReasoningMode: \"non-thinking\",\n\t\tStream: true,\n\t\tTTFTMillis: round2(float64(ttft.Microseconds()) / 1000.0),\n\t\tTPOTMillis: round2(tpot),\n\t\tE2EMillis: round2(float64(e2e.Microseconds()) / 1000.0),\n\t\tOutputToksPerSec: round2(toksPerSec),\n\t\tPromptTokens: prompt,\n\t\tCompletionTokens: completion,\n\t\tReasoningTokens: reasoning,\n\t\tPromptCacheHitTokens: hit,\n\t\tPromptCacheMissTokens: miss,\n\t\tCacheAttribution: attribution,\n\t\tPromptShape: promptShape(\"4K\", \"short\", \"non-thinking\", true),\n\t\t// A single request cannot assert quality parity against another route.\n\t\tQualityParity: \"unknown\",\n\t}, nil\n}\n"}, "7a5df4e0b33f11dfd30eebe22319d1699791608e": {"t": 1785749568.5426295, "n": 21182, "h": "8c14eed21cd1c8dc", "c": "package main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n)\n\n// fak deepseekbench — the DeepSeek V4 Pro/Flash TTFT/TPOT/context-scaling\n// SCORECARD (#3014, under the DeepSeek V4 support program #3006; complements the\n// self-host wire-readiness runbook #3013).\n//\n// It emits one JSONL row per (model × context bucket × output target × reasoning\n// mode × stream/non-stream) cell with the performance axes the issue locks: TTFT,\n// TPOT, E2E, output tok/s, prompt/completion/reasoning tokens, and the provider's\n// prompt-cache hit/miss counters. V4 Pro and V4 Flash are BOTH in the same\n// scorecard — Flash behavior is never inferred from Pro.\n//\n// TWO modes, one schema:\n//\n//\tfak deepseekbench DEFAULT: a no-key DRY-RUN FIXTURE. Every latency\n//\t number is a labelled PLACEHOLDER (measurement=\n//\t \"dry-run-fixture\"), NOT a measurement — the fixture\n//\t exists to lock the row schema and prove the harness\n//\t runs keyless in CI, exactly like the #3013 smoke\n//\t that skips cleanly with no upstream.\n//\tfak deepseekbench --live --spend OPT-IN LIVE run. Refuses unless DEEPSEEK_API_KEY is\n//\t set AND --spend is passed (an explicit money ack).\n//\t Only a live row carries measurement=\"live\" and\n//\t speed_provenance=\"provider-observed\".\n//\n// HONESTY FENCE: this scorecard reports OBSERVED PROVIDER SPEED. A DeepSeek number is\n// never a fak-authored saving — the split is the same OBSERVED-vs-WITNESSED discipline\n// internal/gateway/deepseek_pricing.go applies to the cache counters. The scorecard's\n// speedup line REFUSES to print a delta unless the two rows share a prompt shape and a\n// verified quality parity and are both live measurements (see compareSpeedup).\n//\n//\tfak deepseekbench --out rows.jsonl write the JSONL rows to a file\n//\tfak deepseekbench --base-url URL --model M point the live run at a self-hosted\n//\t OpenAI-compatible endpoint (default hosted)\nfunc cmdDeepSeekBench(argv []string) { os.Exit(runDeepSeekBench(os.Stdout, os.Stderr, argv)) }\n\n// DeepSeekBenchRow is ONE scorecard cell. The JSON tags are the locked schema the\n// field-lock test (TestDeepSeekBenchRequiredFields) pins — adding/renaming a field\n// without updating requiredBenchFields fails the test on purpose.\ntype DeepSeekBenchRow struct {\n\t// Provenance / honesty — read these BEFORE any latency number.\n\tMeasurement string `json:\"measurement\"` // \"dry-run-fixture\" | \"live\"\n\tSpeedProvenance string `json:\"speed_provenance\"` // \"fixture-placeholder-not-measured\" | \"provider-observed\"\n\n\t// Route identity.\n\tModelID string `json:\"model_id\"`\n\tProviderRoute string `json:\"provider_route\"` // e.g. \"deepseek\" | \"anthropic\" | \"openai-compatible\"\n\tEngineRoute string `json:\"engine_route\"` // e.g. \"hosted-api\" | \"vllm\" | \"sglang\"\n\tHosting string `json:\"hosting\"` // \"hosted\" | \"self-hosted\"\n\n\t// Prompt / output shape.\n\tContextBucket string `json:\"context_bucket\"` // 4K | 32K | 128K | 512K | 1M\n\tOutputTarget string `json:\"output_target\"` // short | 1K | 8K | long-reasoning\n\tReasoningMode string `json:\"reasoning_mode\"` // non-thinking | high | max\n\tStream bool `json:\"stream\"`\n\n\t// Latency / throughput.\n\tTTFTMillis float64 `json:\"ttft_ms\"`\n\tTPOTMillis float64 `json:\"tpot_ms\"`\n\tE2EMillis float64 `json:\"e2e_ms\"`\n\tOutputToksPerSec float64 `json:\"output_toks_per_s\"`\n\n\t// Token counters (reasoning_tokens is 0 when the provider does not expose them).\n\tPromptTokens int `json:\"prompt_tokens\"`\n\tCompletionTokens int `json:\"completion_tokens\"`\n\tReasoningTokens int `json:\"reasoning_tokens\"`\n\n\t// Prompt-cache counters + attribution (provider-observed; never fak-authored).\n\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\tCacheAttribution string `json:\"cache_attribution\"` // \"provider-observed\" | \"unknown-dry-run\"\n\n\t// Comparability keys — the scorecard refuses a speedup unless these line up.\n\tPromptShape string `json:\"prompt_shape\"` // shape descriptor (bucket|output|reasoning|stream)\n\tQualityParity string `json:\"quality_parity\"` // \"unknown\" | \"verified\" | \"differs\"\n}\n\n// requiredBenchFields is the locked set of JSON keys every emitted row MUST carry.\n// The field-lock test marshals a row and asserts each key is present, so the issue's\n// required-field list can never silently drift.\nfunc requiredBenchFields() []string {\n\treturn []string{\n\t\t\"measurement\", \"speed_provenance\",\n\t\t\"model_id\", \"provider_route\", \"engine_route\", \"hosting\",\n\t\t\"context_bucket\", \"output_target\", \"reasoning_mode\", \"stream\",\n\t\t\"ttft_ms\", \"tpot_ms\", \"e2e_ms\", \"output_toks_per_s\",\n\t\t\"prompt_tokens\", \"completion_tokens\", \"reasoning_tokens\",\n\t\t\"prompt_cache_hit_tokens\", \"prompt_cache_miss_tokens\", \"cache_attribution\",\n\t\t\"prompt_shape\", \"quality_parity\",\n\t}\n}\n\n// The locked axis vocabularies (the issue's buckets/targets/modes).\nvar (\n\tbenchContextBuckets = []string{\"4K\", \"32K\", \"128K\", \"512K\", \"1M\"}\n\tbenchOutputTargets = []string{\"short\", \"1K\", \"8K\", \"long-reasoning\"}\n\tbenchReasoningModes = []string{\"non-thinking\", \"high\", \"max\"}\n)\n\n// contextBucketTokens maps a bucket label to its nominal prompt-token count.\nfunc contextBucketTokens(bucket string) int {\n\tswitch bucket {\n\tcase \"4K\":\n\t\treturn 4096\n\tcase \"32K\":\n\t\treturn 32768\n\tcase \"128K\":\n\t\treturn 131072\n\tcase \"512K\":\n\t\treturn 524288\n\tcase \"1M\":\n\t\treturn 1048576\n\t}\n\treturn 0\n}\n\n// outputTargetTokens maps an output target to its nominal completion-token count.\nfunc outputTargetTokens(target string) int {\n\tswitch target {\n\tcase \"short\":\n\t\treturn 64\n\tcase \"1K\":\n\t\treturn 1024\n\tcase \"8K\":\n\t\treturn 8192\n\tcase \"long-reasoning\":\n\t\treturn 16384\n\t}\n\treturn 0\n}\n\n// promptShape is the comparability key: two rows are comparable only when it matches.\nfunc promptShape(bucket, output, reasoning string, stream bool) string {\n\treturn fmt.Sprintf(\"%s|%s|%s|stream=%t\", bucket, output, reasoning, stream)\n}\n\n// benchModel is one model's fixture-latency parameters. These are PLACEHOLDERS used\n// only to shape the dry-run fixture; they are NOT measurements and never leave a\n// dry-run row (measurement stays \"dry-run-fixture\").\ntype benchModel struct {\n\tid string\n\tprefillMsPer float64 // fixture prefill ms per 1K prompt tokens\n\ttpotMs float64 // fixture per-output-token ms\n}\n\n// deepseekFixtureModels are V4 Pro and V4 Flash — both, always, side by side. The\n// numbers reflect only the coarse expectation that the 49B-active Pro decodes slower\n// per token than the 13B-active Flash; they are labelled placeholders, not evidence.\nfunc deepseekFixtureModels() []benchModel {\n\treturn []benchModel{\n\t\t{id: \"deepseek-v4-pro\", prefillMsPer: 0.18, tpotMs: 22},\n\t\t{id: \"deepseek-v4-flash\", prefillMsPer: 0.09, tpotMs: 9},\n\t}\n}\n\n// dryRunRows builds the deterministic no-key fixture: the full model × bucket ×\n// output × reasoning × stream matrix (skipping the contradictory non-thinking +\n// long-reasoning cells), plus one fak-route baseline row so the scorecard has a\n// same-harness comparand to (correctly) REFUSE comparing against in dry-run.\nfunc dryRunRows() []DeepSeekBenchRow {\n\tvar rows []DeepSeekBenchRow\n\tfor _, m := range deepseekFixtureModels() {\n\t\tfor _, bucket := range benchContextBuckets {\n\t\t\tfor _, output := range benchOutputTargets {\n\t\t\t\tfor _, reasoning := range benchReasoningModes {\n\t\t\t\t\tif output == \"long-reasoning\" && reasoning == \"non-thinking\" {\n\t\t\t\t\t\tcontinue // a non-thinking route does not emit a long reasoning trace\n\t\t\t\t\t}\n\t\t\t\t\tfor _, stream := range []bool{true, false} {\n\t\t\t\t\t\trows = append(rows, fixtureRow(m, bucket, output, reasoning, stream))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// One existing-fak-route baseline row, same harness, same schema. It is also a\n\t// fixture, so the scorecard MUST refuse to compare it (measurement != live).\n\trows = append(rows, DeepSeekBenchRow{\n\t\tMeasurement: \"dry-run-fixture\",\n\t\tSpeedProvenance: \"fixture-placeholder-not-measured\",\n\t\tModelID: \"claude-sonnet-5\",\n\t\tProviderRoute: \"anthropic\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: \"32K\",\n\t\tOutputTarget: \"1K\",\n\t\tReasoningMode: \"non-thinking\",\n\t\tStream: true,\n\t\tCacheAttribution: \"unknown-dry-run\",\n\t\tPromptShape: promptShape(\"32K\", \"1K\", \"non-thinking\", true),\n\t\tQualityParity: \"unknown\",\n\t\tPromptTokens: contextBucketTokens(\"32K\"),\n\t\tCompletionTokens: outputTargetTokens(\"1K\"),\n\t})\n\treturn rows\n}\n\n// fixtureRow computes one deterministic placeholder row. No time/rand: every number\n// is a pure function of the axes, so the fixture is byte-stable across runs.\nfunc fixtureRow(m benchModel, bucket, output, reasoning string, stream bool) DeepSeekBenchRow {\n\tprompt := contextBucketTokens(bucket)\n\tcompletion := outputTargetTokens(output)\n\treasoningTokens := 0\n\tswitch reasoning {\n\tcase \"high\":\n\t\treasoningTokens = completion\n\tcase \"max\":\n\t\treasoningTokens = completion * 3\n\t}\n\tgenTokens := completion + reasoningTokens\n\tttft := (float64(prompt) / 1000.0) * m.prefillMsPer\n\ttpot := m.tpotMs\n\tvar e2e, toksPerSec float64\n\tif stream {\n\t\te2e = ttft + tpot*float64(genTokens)\n\t} else {\n\t\t// Non-stream: no incremental token stream, so TPOT is not observable; the whole\n\t\t// generation lands at once and TTFT collapses into E2E.\n\t\ttpot = 0\n\t\te2e = ttft + m.tpotMs*float64(genTokens)\n\t\tttft = e2e\n\t}\n\tif e2e > 0 {\n\t\ttoksPerSec = float64(genTokens) / (e2e / 1000.0)\n\t}\n\treturn DeepSeekBenchRow{\n\t\tMeasurement: \"dry-run-fixture\",\n\t\tSpeedProvenance: \"fixture-placeholder-not-measured\",\n\t\tModelID: m.id,\n\t\tProviderRoute: \"deepseek\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: bucket,\n\t\tOutputTarget: output,\n\t\tReasoningMode: reasoning,\n\t\tStream: stream,\n\t\tTTFTMillis: round2(ttft),\n\t\tTPOTMillis: round2(tpot),\n\t\tE2EMillis: round2(e2e),\n\t\tOutputToksPerSec: round2(toksPerSec),\n\t\tPromptTokens: prompt,\n\t\tCompletionTokens: completion,\n\t\tReasoningTokens: reasoningTokens,\n\t\t// The fixture asserts nothing about cache behavior — a real hit/miss split is a\n\t\t// provider-observed number only a live run may fill.\n\t\tPromptCacheHitTokens: 0,\n\t\tPromptCacheMissTokens: 0,\n\t\tCacheAttribution: \"unknown-dry-run\",\n\t\tPromptShape: promptShape(bucket, output, reasoning, stream),\n\t\tQualityParity: \"unknown\",\n\t}\n}\n\nfunc round2(v float64) float64 {\n\treturn float64(int64(v*100+0.5)) / 100\n}\n\n// compareSpeedup is the honesty gate the issue demands: it prints a speedup delta\n// ONLY when the DeepSeek row and the baseline row (a) share a prompt shape, (b) both\n// carry a verified quality parity, and (c) are both live measurements. Any other case\n// returns a \"[NOT COMPARABLE: …]\" line and printed=false — a dry-run fixture, a shape\n// mismatch, or an unverified parity can never surface as a speed headline.\nfunc compareSpeedup(subject, baseline DeepSeekBenchRow) (line string, printed bool) {\n\tswitch {\n\tcase subject.Measurement != \"live\" || baseline.Measurement != \"live\":\n\t\treturn \"[NOT COMPARABLE: dry-run fixture — no measured latency; run --live with a parity check]\", false\n\tcase subject.PromptShape != baseline.PromptShape:\n\t\treturn fmt.Sprintf(\"[NOT COMPARABLE: prompt shape differs (%s vs %s)]\", subject.PromptShape, baseline.PromptShape), false\n\tcase subject.QualityParity != \"verified\" || baseline.QualityParity != \"verified\":\n\t\treturn \"[NOT COMPARABLE: quality parity not verified for both rows]\", false\n\tcase baseline.E2EMillis <= 0 || subject.E2EMillis <= 0:\n\t\treturn \"[NOT COMPARABLE: a measured E2E is missing]\", false\n\t}\n\tratio := baseline.E2EMillis / subject.E2EMillis\n\treturn fmt.Sprintf(\"OBSERVED provider speed: %s is %.2f× the E2E of %s at shape %s (provider-observed, not a fak-authored saving)\",\n\t\tsubject.ModelID, ratio, baseline.ModelID, subject.PromptShape), true\n}\n\n// runDeepSeekBench is the testable core (exit code, no os.Exit): 0 ok, 1 a run error,\n// 2 a usage/gating error.\nfunc runDeepSeekBench(stdout, stderr io.Writer, argv []string) int {\n\tfs := flag.NewFlagSet(\"deepseekbench\", flag.ContinueOnError)\n\tfs.SetOutput(stderr)\n\tlive := fs.Bool(\"live\", false, \"opt-in LIVE run (requires DEEPSEEK_API_KEY and --spend); default is a no-key dry-run fixture\")\n\tspend := fs.Bool(\"spend\", false, \"explicit acknowledgement that a --live run costs money\")\n\tbaseURL := fs.String(\"base-url\", \"https://api.deepseek.com\", \"OpenAI-compatible root for a --live run (point at a self-hosted vLLM/SGLang endpoint for the self-hosted arm)\")\n\tmodel := fs.String(\"model\", \"deepseek-v4-flash\", \"model id to route for a --live run\")\n\toutPath := fs.String(\"out\", \"\", \"write the JSONL rows to this file (default: stdout)\")\n\tif rc, ok := parseFlagsOrHelp(fs, argv); !ok {\n\t\treturn rc\n\t}\n\n\tvar rows []DeepSeekBenchRow\n\tif *live {\n\t\tgot, rc := liveRows(stderr, *baseURL, *model, *spend)\n\t\tif rc != 0 {\n\t\t\treturn rc\n\t\t}\n\t\trows = got\n\t} else {\n\t\trows = dryRunRows()\n\t}\n\n\tsink := stdout\n\tif *outPath != \"\" {\n\t\tf, err := os.Create(*outPath)\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", err)\n\t\t\treturn 1\n\t\t}\n\t\tdefer f.Close()\n\t\tsink = f\n\t}\n\tif err := writeBenchJSONL(sink, rows); err != nil {\n\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", err)\n\t\treturn 1\n\t}\n\n\tprintBenchSummary(stderr, rows, *live)\n\treturn 0\n}\n\n// writeBenchJSONL emits one compact JSON object per line — the locked scorecard format.\nfunc writeBenchJSONL(w io.Writer, rows []DeepSeekBenchRow) error {\n\tbw := bufio.NewWriter(w)\n\tenc := json.NewEncoder(bw)\n\tfor _, r := range rows {\n\t\tif err := enc.Encode(r); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn bw.Flush()\n}\n\n// printBenchSummary writes the human-facing coverage + honesty summary to stderr (the\n// JSONL rows themselves are the machine artifact on stdout/--out).\nfunc printBenchSummary(w io.Writer, rows []DeepSeekBenchRow, live bool) {\n\tmodels := map[string]int{}\n\tfor _, r := range rows {\n\t\tmodels[r.ModelID]++\n\t}\n\tids := make([]string, 0, len(models))\n\tfor id := range models {\n\t\tids = append(ids, id)\n\t}\n\tsort.Strings(ids)\n\tfmt.Fprintln(w, \"== fak deepseekbench — DeepSeek V4 Pro/Flash TTFT/TPOT/context scorecard (#3014) ==\")\n\tif live {\n\t\tfmt.Fprintln(w, \"mode: LIVE (measurement=\\\"live\\\"; latency is provider-observed)\")\n\t} else {\n\t\tfmt.Fprintln(w, \"mode: DRY-RUN FIXTURE (measurement=\\\"dry-run-fixture\\\"; every latency is a labelled placeholder, NOT measured)\")\n\t}\n\tfmt.Fprintf(w, \"rows: %d across models:\\n\", len(rows))\n\tfor _, id := range ids {\n\t\tfmt.Fprintf(w, \" %-22s %d rows\\n\", id, models[id])\n\t}\n\t// Demonstrate the speedup-refusal gate on the first two DeepSeek/baseline rows.\n\tline, _ := scorecardExample(rows)\n\tfmt.Fprintf(w, \"scorecard speedup gate: %s\\n\", line)\n\tfmt.Fprintln(w, \"OBSERVED provider speed is never a fak-authored saving — see docs/benchmarks/DEEPSEEK-V4-PERF-SCORECARD.md\")\n\tif !live {\n\t\tfmt.Fprintln(w, \"live recipe: DEEPSEEK_API_KEY=… fak deepseekbench --live --spend --model deepseek-v4-pro\")\n\t}\n}\n\n// scorecardExample picks a DeepSeek subject row and a non-DeepSeek baseline row (if\n// present) and runs the refusal gate over them, so the summary always shows what the\n// gate decides on the current rows.\nfunc scorecardExample(rows []DeepSeekBenchRow) (string, bool) {\n\tvar subject, baseline *DeepSeekBenchRow\n\tfor i := range rows {\n\t\tif rows[i].ProviderRoute == \"deepseek\" && subject == nil {\n\t\t\tsubject = &rows[i]\n\t\t}\n\t\tif rows[i].ProviderRoute != \"deepseek\" && baseline == nil {\n\t\t\tbaseline = &rows[i]\n\t\t}\n\t}\n\tif subject == nil || baseline == nil {\n\t\treturn \"[NOT COMPARABLE: need one DeepSeek row and one baseline row]\", false\n\t}\n\treturn compareSpeedup(*subject, *baseline)\n}\n\n// --- live measurement arm (opt-in, gated, unwitnessed in CI) --------------------\n\n// liveRows gates the live arm and, when admitted, measures one streamed request. It\n// returns (rows, exitCode); a non-zero code means the gate refused BEFORE any network.\nfunc liveRows(stderr io.Writer, baseURL, model string, spend bool) ([]DeepSeekBenchRow, int) {\n\tkey := strings.TrimSpace(os.Getenv(\"DEEPSEEK_API_KEY\"))\n\tif key == \"\" {\n\t\tfmt.Fprintln(stderr, \"fak deepseekbench: --live needs DEEPSEEK_API_KEY set (refusing before any network call)\")\n\t\treturn nil, 2\n\t}\n\tif !spend {\n\t\tfmt.Fprintln(stderr, \"fak deepseekbench: --live costs money — pass --spend to acknowledge (refusing)\")\n\t\treturn nil, 2\n\t}\n\trow, err := measureStreamed(baseURL, key, model)\n\tif err != nil {\n\t\tfmt.Fprintln(stderr, \"fak deepseekbench: live run failed:\", err)\n\t\treturn nil, 1\n\t}\n\treturn []DeepSeekBenchRow{row}, 0\n}\n\n// measureStreamed issues one streaming chat/completions request and times TTFT (first\n// content delta), TPOT (mean inter-delta), E2E, and reads the final usage block for the\n// token + prompt-cache counters. It is the untested-in-CI arm: it only runs behind the\n// key+spend gate, mirroring the #3013 self-host smoke that skips cleanly without a node.\nfunc measureStreamed(baseURL, key, model string) (DeepSeekBenchRow, error) {\n\tbody, _ := json.Marshal(map[string]any{\n\t\t\"model\": model,\n\t\t\"stream\": true,\n\t\t\"messages\": []map[string]string{{\"role\": \"user\", \"content\": \"Reply with a short sentence.\"}},\n\t\t\"stream_options\": map[string]any{\"include_usage\": true},\n\t})\n\turl := strings.TrimRight(baseURL, \"/\") + \"/chat/completions\"\n\treq, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))\n\tif err != nil {\n\t\treturn DeepSeekBenchRow{}, err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+key)\n\n\tclient := &http.Client{Timeout: 5 * time.Minute}\n\tstart := time.Now()\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn DeepSeekBenchRow{}, err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\tsnippet, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\n\t\treturn DeepSeekBenchRow{}, fmt.Errorf(\"upstream status %d: %s\", resp.StatusCode, strings.TrimSpace(string(snippet)))\n\t}\n\n\tvar ttft time.Duration\n\tvar deltas int\n\tvar lastDelta time.Time\n\tvar interSum time.Duration\n\tprompt, completion, reasoning, hit, miss := 0, 0, 0, 0, 0\n\tsc := bufio.NewScanner(resp.Body)\n\tsc.Buffer(make([]byte, 0, 64*1024), 4*1024*1024)\n\tfor sc.Scan() {\n\t\tline := strings.TrimSpace(sc.Text())\n\t\tif !strings.HasPrefix(line, \"data:\") {\n\t\t\tcontinue\n\t\t}\n\t\tdata := strings.TrimSpace(strings.TrimPrefix(line, \"data:\"))\n\t\tif data == \"[DONE]\" {\n\t\t\tbreak\n\t\t}\n\t\tvar ev struct {\n\t\t\tChoices []struct {\n\t\t\t\tDelta struct {\n\t\t\t\t\tContent string `json:\"content\"`\n\t\t\t\t\tReasoningContent string `json:\"reasoning_content\"`\n\t\t\t\t} `json:\"delta\"`\n\t\t\t} `json:\"choices\"`\n\t\t\tUsage *struct {\n\t\t\t\tPromptTokens int `json:\"prompt_tokens\"`\n\t\t\t\tCompletionTokens int `json:\"completion_tokens\"`\n\t\t\t\tReasoningTokens int `json:\"reasoning_tokens\"`\n\t\t\t\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\t\t\t\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\t\t\t} `json:\"usage\"`\n\t\t}\n\t\tif err := json.Unmarshal([]byte(data), &ev); err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif len(ev.Choices) > 0 && (ev.Choices[0].Delta.Content != \"\" || ev.Choices[0].Delta.ReasoningContent != \"\") {\n\t\t\tnow := time.Now()\n\t\t\tif ttft == 0 {\n\t\t\t\tttft = now.Sub(start)\n\t\t\t} else {\n\t\t\t\tinterSum += now.Sub(lastDelta)\n\t\t\t}\n\t\t\tlastDelta = now\n\t\t\tdeltas++\n\t\t}\n\t\tif ev.Usage != nil {\n\t\t\tprompt = ev.Usage.PromptTokens\n\t\t\tcompletion = ev.Usage.CompletionTokens\n\t\t\treasoning = ev.Usage.ReasoningTokens\n\t\t\thit = ev.Usage.PromptCacheHitTokens\n\t\t\tmiss = ev.Usage.PromptCacheMissTokens\n\t\t}\n\t}\n\tif err := sc.Err(); err != nil {\n\t\treturn DeepSeekBenchRow{}, err\n\t}\n\te2e := time.Since(start)\n\ttpot := 0.0\n\tif deltas > 1 {\n\t\ttpot = float64(interSum.Milliseconds()) / float64(deltas-1)\n\t}\n\ttoksPerSec := 0.0\n\tif e2e > 0 && completion > 0 {\n\t\ttoksPerSec = float64(completion) / e2e.Seconds()\n\t}\n\tattribution := \"unknown\"\n\tif hit > 0 || miss > 0 {\n\t\tattribution = \"provider-observed\"\n\t}\n\treturn DeepSeekBenchRow{\n\t\tMeasurement: \"live\",\n\t\tSpeedProvenance: \"provider-observed\",\n\t\tModelID: model,\n\t\tProviderRoute: \"deepseek\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: \"4K\",\n\t\tOutputTarget: \"short\",\n\t\tReasoningMode: \"non-thinking\",\n\t\tStream: true,\n\t\tTTFTMillis: round2(float64(ttft.Microseconds()) / 1000.0),\n\t\tTPOTMillis: round2(tpot),\n\t\tE2EMillis: round2(float64(e2e.Microseconds()) / 1000.0),\n\t\tOutputToksPerSec: round2(toksPerSec),\n\t\tPromptTokens: prompt,\n\t\tCompletionTokens: completion,\n\t\tReasoningTokens: reasoning,\n\t\tPromptCacheHitTokens: hit,\n\t\tPromptCacheMissTokens: miss,\n\t\tCacheAttribution: attribution,\n\t\tPromptShape: promptShape(\"4K\", \"short\", \"non-thinking\", true),\n\t\t// A single request cannot assert quality parity against another route.\n\t\tQualityParity: \"unknown\",\n\t}, nil\n}\n"}, "8e69c3118c28ceab9febb9e1775718c077d2c08d": {"t": 1785749569.6860425, "n": 21164, "h": "03fa51a45144affc", "c": "package main\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n)\n\n// fak deepseekbench — the DeepSeek V4 Pro/Flash TTFT/TPOT/context-scaling\n// SCORECARD (#3014, under the DeepSeek V4 support program #3006; complements the\n// self-host wire-readiness runbook #3013).\n//\n// It emits one JSONL row per (model × context bucket × output target × reasoning\n// mode × stream/non-stream) cell with the performance axes the issue locks: TTFT,\n// TPOT, E2E, output tok/s, prompt/completion/reasoning tokens, and the provider's\n// prompt-cache hit/miss counters. V4 Pro and V4 Flash are BOTH in the same\n// scorecard — Flash behavior is never inferred from Pro.\n//\n// TWO modes, one schema:\n//\n//\tfak deepseekbench DEFAULT: a no-key DRY-RUN FIXTURE. Every latency\n//\t number is a labelled PLACEHOLDER (measurement=\n//\t \"dry-run-fixture\"), NOT a measurement — the fixture\n//\t exists to lock the row schema and prove the harness\n//\t runs keyless in CI, exactly like the #3013 smoke\n//\t that skips cleanly with no upstream.\n//\tfak deepseekbench --live --spend OPT-IN LIVE run. Refuses unless DEEPSEEK_API_KEY is\n//\t set AND --spend is passed (an explicit money ack).\n//\t Only a live row carries measurement=\"live\" and\n//\t speed_provenance=\"provider-observed\".\n//\n// HONESTY FENCE: this scorecard reports OBSERVED PROVIDER SPEED. A DeepSeek number is\n// never a fak-authored saving — the split is the same OBSERVED-vs-WITNESSED discipline\n// internal/gateway/deepseek_pricing.go applies to the cache counters. The scorecard's\n// speedup line REFUSES to print a delta unless the two rows share a prompt shape and a\n// verified quality parity and are both live measurements (see compareSpeedup).\n//\n//\tfak deepseekbench --out rows.jsonl write the JSONL rows to a file\n//\tfak deepseekbench --base-url URL --model M point the live run at a self-hosted\n//\t OpenAI-compatible endpoint (default hosted)\nfunc cmdDeepSeekBench(argv []string) { os.Exit(runDeepSeekBench(os.Stdout, os.Stderr, argv)) }\n\n// DeepSeekBenchRow is ONE scorecard cell. The JSON tags are the locked schema the\n// field-lock test (TestDeepSeekBenchRequiredFields) pins — adding/renaming a field\n// without updating requiredBenchFields fails the test on purpose.\ntype DeepSeekBenchRow struct {\n\t// Provenance / honesty — read these BEFORE any latency number.\n\tMeasurement string `json:\"measurement\"` // \"dry-run-fixture\" | \"live\"\n\tSpeedProvenance string `json:\"speed_provenance\"` // \"fixture-placeholder-not-measured\" | \"provider-observed\"\n\n\t// Route identity.\n\tModelID string `json:\"model_id\"`\n\tProviderRoute string `json:\"provider_route\"` // e.g. \"deepseek\" | \"anthropic\" | \"openai-compatible\"\n\tEngineRoute string `json:\"engine_route\"` // e.g. \"hosted-api\" | \"vllm\" | \"sglang\"\n\tHosting string `json:\"hosting\"` // \"hosted\" | \"self-hosted\"\n\n\t// Prompt / output shape.\n\tContextBucket string `json:\"context_bucket\"` // 4K | 32K | 128K | 512K | 1M\n\tOutputTarget string `json:\"output_target\"` // short | 1K | 8K | long-reasoning\n\tReasoningMode string `json:\"reasoning_mode\"` // non-thinking | high | max\n\tStream bool `json:\"stream\"`\n\n\t// Latency / throughput.\n\tTTFTMillis float64 `json:\"ttft_ms\"`\n\tTPOTMillis float64 `json:\"tpot_ms\"`\n\tE2EMillis float64 `json:\"e2e_ms\"`\n\tOutputToksPerSec float64 `json:\"output_toks_per_s\"`\n\n\t// Token counters (reasoning_tokens is 0 when the provider does not expose them).\n\tPromptTokens int `json:\"prompt_tokens\"`\n\tCompletionTokens int `json:\"completion_tokens\"`\n\tReasoningTokens int `json:\"reasoning_tokens\"`\n\n\t// Prompt-cache counters + attribution (provider-observed; never fak-authored).\n\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\tCacheAttribution string `json:\"cache_attribution\"` // \"provider-observed\" | \"unknown-dry-run\"\n\n\t// Comparability keys — the scorecard refuses a speedup unless these line up.\n\tPromptShape string `json:\"prompt_shape\"` // shape descriptor (bucket|output|reasoning|stream)\n\tQualityParity string `json:\"quality_parity\"` // \"unknown\" | \"verified\" | \"differs\"\n}\n\n// requiredBenchFields is the locked set of JSON keys every emitted row MUST carry.\n// The field-lock test marshals a row and asserts each key is present, so the issue's\n// required-field list can never silently drift.\nfunc requiredBenchFields() []string {\n\treturn []string{\n\t\t\"measurement\", \"speed_provenance\",\n\t\t\"model_id\", \"provider_route\", \"engine_route\", \"hosting\",\n\t\t\"context_bucket\", \"output_target\", \"reasoning_mode\", \"stream\",\n\t\t\"ttft_ms\", \"tpot_ms\", \"e2e_ms\", \"output_toks_per_s\",\n\t\t\"prompt_tokens\", \"completion_tokens\", \"reasoning_tokens\",\n\t\t\"prompt_cache_hit_tokens\", \"prompt_cache_miss_tokens\", \"cache_attribution\",\n\t\t\"prompt_shape\", \"quality_parity\",\n\t}\n}\n\n// The locked axis vocabularies (the issue's buckets/targets/modes).\nvar (\n\tbenchContextBuckets = []string{\"4K\", \"32K\", \"128K\", \"512K\", \"1M\"}\n\tbenchOutputTargets = []string{\"short\", \"1K\", \"8K\", \"long-reasoning\"}\n\tbenchReasoningModes = []string{\"non-thinking\", \"high\", \"max\"}\n)\n\n// contextBucketTokens maps a bucket label to its nominal prompt-token count.\nfunc contextBucketTokens(bucket string) int {\n\tswitch bucket {\n\tcase \"4K\":\n\t\treturn 4096\n\tcase \"32K\":\n\t\treturn 32768\n\tcase \"128K\":\n\t\treturn 131072\n\tcase \"512K\":\n\t\treturn 524288\n\tcase \"1M\":\n\t\treturn 1048576\n\t}\n\treturn 0\n}\n\n// outputTargetTokens maps an output target to its nominal completion-token count.\nfunc outputTargetTokens(target string) int {\n\tswitch target {\n\tcase \"short\":\n\t\treturn 64\n\tcase \"1K\":\n\t\treturn 1024\n\tcase \"8K\":\n\t\treturn 8192\n\tcase \"long-reasoning\":\n\t\treturn 16384\n\t}\n\treturn 0\n}\n\n// promptShape is the comparability key: two rows are comparable only when it matches.\nfunc promptShape(bucket, output, reasoning string, stream bool) string {\n\treturn fmt.Sprintf(\"%s|%s|%s|stream=%t\", bucket, output, reasoning, stream)\n}\n\n// benchModel is one model's fixture-latency parameters. These are PLACEHOLDERS used\n// only to shape the dry-run fixture; they are NOT measurements and never leave a\n// dry-run row (measurement stays \"dry-run-fixture\").\ntype benchModel struct {\n\tid string\n\tprefillMsPer float64 // fixture prefill ms per 1K prompt tokens\n\ttpotMs float64 // fixture per-output-token ms\n}\n\n// deepseekFixtureModels are V4 Pro and V4 Flash — both, always, side by side. The\n// numbers reflect only the coarse expectation that the 49B-active Pro decodes slower\n// per token than the 13B-active Flash; they are labelled placeholders, not evidence.\nfunc deepseekFixtureModels() []benchModel {\n\treturn []benchModel{\n\t\t{id: \"deepseek-v4-pro\", prefillMsPer: 0.18, tpotMs: 22},\n\t\t{id: \"deepseek-v4-flash\", prefillMsPer: 0.09, tpotMs: 9},\n\t}\n}\n\n// dryRunRows builds the deterministic no-key fixture: the full model × bucket ×\n// output × reasoning × stream matrix (skipping the contradictory non-thinking +\n// long-reasoning cells), plus one fak-route baseline row so the scorecard has a\n// same-harness comparand to (correctly) REFUSE comparing against in dry-run.\nfunc dryRunRows() []DeepSeekBenchRow {\n\tvar rows []DeepSeekBenchRow\n\tfor _, m := range deepseekFixtureModels() {\n\t\tfor _, bucket := range benchContextBuckets {\n\t\t\tfor _, output := range benchOutputTargets {\n\t\t\t\tfor _, reasoning := range benchReasoningModes {\n\t\t\t\t\tif output == \"long-reasoning\" && reasoning == \"non-thinking\" {\n\t\t\t\t\t\tcontinue // a non-thinking route does not emit a long reasoning trace\n\t\t\t\t\t}\n\t\t\t\t\tfor _, stream := range []bool{true, false} {\n\t\t\t\t\t\trows = append(rows, fixtureRow(m, bucket, output, reasoning, stream))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// One existing-fak-route baseline row, same harness, same schema. It is also a\n\t// fixture, so the scorecard MUST refuse to compare it (measurement != live).\n\trows = append(rows, DeepSeekBenchRow{\n\t\tMeasurement: \"dry-run-fixture\",\n\t\tSpeedProvenance: \"fixture-placeholder-not-measured\",\n\t\tModelID: \"claude-sonnet-5\",\n\t\tProviderRoute: \"anthropic\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: \"32K\",\n\t\tOutputTarget: \"1K\",\n\t\tReasoningMode: \"non-thinking\",\n\t\tStream: true,\n\t\tCacheAttribution: \"unknown-dry-run\",\n\t\tPromptShape: promptShape(\"32K\", \"1K\", \"non-thinking\", true),\n\t\tQualityParity: \"unknown\",\n\t\tPromptTokens: contextBucketTokens(\"32K\"),\n\t\tCompletionTokens: outputTargetTokens(\"1K\"),\n\t})\n\treturn rows\n}\n\n// fixtureRow computes one deterministic placeholder row. No time/rand: every number\n// is a pure function of the axes, so the fixture is byte-stable across runs.\nfunc fixtureRow(m benchModel, bucket, output, reasoning string, stream bool) DeepSeekBenchRow {\n\tprompt := contextBucketTokens(bucket)\n\tcompletion := outputTargetTokens(output)\n\treasoningTokens := 0\n\tswitch reasoning {\n\tcase \"high\":\n\t\treasoningTokens = completion\n\tcase \"max\":\n\t\treasoningTokens = completion * 3\n\t}\n\tgenTokens := completion + reasoningTokens\n\tttft := (float64(prompt) / 1000.0) * m.prefillMsPer\n\ttpot := m.tpotMs\n\tvar e2e, toksPerSec float64\n\tif stream {\n\t\te2e = ttft + tpot*float64(genTokens)\n\t} else {\n\t\t// Non-stream: no incremental token stream, so TPOT is not observable; the whole\n\t\t// generation lands at once and TTFT collapses into E2E.\n\t\ttpot = 0\n\t\te2e = ttft + m.tpotMs*float64(genTokens)\n\t\tttft = e2e\n\t}\n\tif e2e > 0 {\n\t\ttoksPerSec = float64(genTokens) / (e2e / 1000.0)\n\t}\n\treturn DeepSeekBenchRow{\n\t\tMeasurement: \"dry-run-fixture\",\n\t\tSpeedProvenance: \"fixture-placeholder-not-measured\",\n\t\tModelID: m.id,\n\t\tProviderRoute: \"deepseek\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: bucket,\n\t\tOutputTarget: output,\n\t\tReasoningMode: reasoning,\n\t\tStream: stream,\n\t\tTTFTMillis: round2(ttft),\n\t\tTPOTMillis: round2(tpot),\n\t\tE2EMillis: round2(e2e),\n\t\tOutputToksPerSec: round2(toksPerSec),\n\t\tPromptTokens: prompt,\n\t\tCompletionTokens: completion,\n\t\tReasoningTokens: reasoningTokens,\n\t\t// The fixture asserts nothing about cache behavior — a real hit/miss split is a\n\t\t// provider-observed number only a live run may fill.\n\t\tPromptCacheHitTokens: 0,\n\t\tPromptCacheMissTokens: 0,\n\t\tCacheAttribution: \"unknown-dry-run\",\n\t\tPromptShape: promptShape(bucket, output, reasoning, stream),\n\t\tQualityParity: \"unknown\",\n\t}\n}\n\nfunc round2(v float64) float64 {\n\treturn float64(int64(v*100+0.5)) / 100\n}\n\n// compareSpeedup is the honesty gate the issue demands: it prints a speedup delta\n// ONLY when the DeepSeek row and the baseline row (a) share a prompt shape, (b) both\n// carry a verified quality parity, and (c) are both live measurements. Any other case\n// returns a \"[NOT COMPARABLE: …]\" line and printed=false — a dry-run fixture, a shape\n// mismatch, or an unverified parity can never surface as a speed headline.\nfunc compareSpeedup(subject, baseline DeepSeekBenchRow) (line string, printed bool) {\n\tswitch {\n\tcase subject.Measurement != \"live\" || baseline.Measurement != \"live\":\n\t\treturn \"[NOT COMPARABLE: dry-run fixture — no measured latency; run --live with a parity check]\", false\n\tcase subject.PromptShape != baseline.PromptShape:\n\t\treturn fmt.Sprintf(\"[NOT COMPARABLE: prompt shape differs (%s vs %s)]\", subject.PromptShape, baseline.PromptShape), false\n\tcase subject.QualityParity != \"verified\" || baseline.QualityParity != \"verified\":\n\t\treturn \"[NOT COMPARABLE: quality parity not verified for both rows]\", false\n\tcase baseline.E2EMillis <= 0 || subject.E2EMillis <= 0:\n\t\treturn \"[NOT COMPARABLE: a measured E2E is missing]\", false\n\t}\n\tratio := baseline.E2EMillis / subject.E2EMillis\n\treturn fmt.Sprintf(\"OBSERVED provider speed: %s is %.2f× the E2E of %s at shape %s (provider-observed, not a fak-authored saving)\",\n\t\tsubject.ModelID, ratio, baseline.ModelID, subject.PromptShape), true\n}\n\n// runDeepSeekBench is the testable core (exit code, no os.Exit): 0 ok, 1 a run error,\n// 2 a usage/gating error.\nfunc runDeepSeekBench(stdout, stderr io.Writer, argv []string) int {\n\tfs := flag.NewFlagSet(\"deepseekbench\", flag.ContinueOnError)\n\tfs.SetOutput(stderr)\n\tlive := fs.Bool(\"live\", false, \"opt-in LIVE run (requires DEEPSEEK_API_KEY and --spend); default is a no-key dry-run fixture\")\n\tspend := fs.Bool(\"spend\", false, \"explicit acknowledgement that a --live run costs money\")\n\tbaseURL := fs.String(\"base-url\", \"https://api.deepseek.com\", \"OpenAI-compatible root for a --live run (point at a self-hosted vLLM/SGLang endpoint for the self-hosted arm)\")\n\tmodel := fs.String(\"model\", \"deepseek-v4-flash\", \"model id to route for a --live run\")\n\toutPath := fs.String(\"out\", \"\", \"write the JSONL rows to this file (default: stdout)\")\n\tif rc, ok := parseFlagsOrHelp(fs, argv); !ok {\n\t\treturn rc\n\t}\n\n\tvar rows []DeepSeekBenchRow\n\tif *live {\n\t\tgot, rc := liveRows(stderr, *baseURL, *model, *spend)\n\t\tif rc != 0 {\n\t\t\treturn rc\n\t\t}\n\t\trows = got\n\t} else {\n\t\trows = dryRunRows()\n\t}\n\n\tsink := stdout\n\tif *outPath != \"\" {\n\t\tf, err := os.Create(*outPath)\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", err)\n\t\t\treturn 1\n\t\t}\n\t\tdefer f.Close()\n\t\tsink = f\n\t}\n\tif err := writeBenchJSONL(sink, rows); err != nil {\n\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", err)\n\t\treturn 1\n\t}\n\n\tprintBenchSummary(stderr, rows, *live)\n\treturn 0\n}\n\n// writeBenchJSONL emits one compact JSON object per line — the locked scorecard format.\nfunc writeBenchJSONL(w io.Writer, rows []DeepSeekBenchRow) error {\n\tbw := bufio.NewWriter(w)\n\tenc := json.NewEncoder(bw)\n\tfor _, r := range rows {\n\t\tif err := enc.Encode(r); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn bw.Flush()\n}\n\n// printBenchSummary writes the human-facing coverage + honesty summary to stderr (the\n// JSONL rows themselves are the machine artifact on stdout/--out).\nfunc printBenchSummary(w io.Writer, rows []DeepSeekBenchRow, live bool) {\n\tmodels := map[string]int{}\n\tfor _, r := range rows {\n\t\tmodels[r.ModelID]++\n\t}\n\tids := make([]string, 0, len(models))\n\tfor id := range models {\n\t\tids = append(ids, id)\n\t}\n\tsort.Strings(ids)\n\tfmt.Fprintln(w, \"== fak deepseekbench — DeepSeek V4 Pro/Flash TTFT/TPOT/context scorecard (#3014) ==\")\n\tif live {\n\t\tfmt.Fprintln(w, \"mode: LIVE (measurement=\\\"live\\\"; latency is provider-observed)\")\n\t} else {\n\t\tfmt.Fprintln(w, \"mode: DRY-RUN FIXTURE (measurement=\\\"dry-run-fixture\\\"; every latency is a labelled placeholder, NOT measured)\")\n\t}\n\tfmt.Fprintf(w, \"rows: %d across models:\\n\", len(rows))\n\tfor _, id := range ids {\n\t\tfmt.Fprintf(w, \" %-22s %d rows\\n\", id, models[id])\n\t}\n\t// Demonstrate the speedup-refusal gate on the first two DeepSeek/baseline rows.\n\tline, _ := scorecardExample(rows)\n\tfmt.Fprintf(w, \"scorecard speedup gate: %s\\n\", line)\n\tfmt.Fprintln(w, \"OBSERVED provider speed is never a fak-authored saving — see docs/benchmarks/DEEPSEEK-V4-PERF-SCORECARD.md\")\n\tif !live {\n\t\tfmt.Fprintln(w, \"live recipe: DEEPSEEK_API_KEY=… fak deepseekbench --live --spend --model deepseek-v4-pro\")\n\t}\n}\n\n// scorecardExample picks a DeepSeek subject row and a non-DeepSeek baseline row (if\n// present) and runs the refusal gate over them, so the summary always shows what the\n// gate decides on the current rows.\nfunc scorecardExample(rows []DeepSeekBenchRow) (string, bool) {\n\tvar subject, baseline *DeepSeekBenchRow\n\tfor i := range rows {\n\t\tif rows[i].ProviderRoute == \"deepseek\" && subject == nil {\n\t\t\tsubject = &rows[i]\n\t\t}\n\t\tif rows[i].ProviderRoute != \"deepseek\" && baseline == nil {\n\t\t\tbaseline = &rows[i]\n\t\t}\n\t}\n\tif subject == nil || baseline == nil {\n\t\treturn \"[NOT COMPARABLE: need one DeepSeek row and one baseline row]\", false\n\t}\n\treturn compareSpeedup(*subject, *baseline)\n}\n\n// --- live measurement arm (opt-in, gated, unwitnessed in CI) --------------------\n\n// liveRows gates the live arm and, when admitted, measures one streamed request. It\n// returns (rows, exitCode); a non-zero code means the gate refused BEFORE any network.\nfunc liveRows(stderr io.Writer, baseURL, model string, spend bool) ([]DeepSeekBenchRow, int) {\n\tkey := strings.TrimSpace(os.Getenv(\"DEEPSEEK_API_KEY\"))\n\tif key == \"\" {\n\t\tfmt.Fprintln(stderr, \"fak deepseekbench: --live needs DEEPSEEK_API_KEY set (refusing before any network call)\")\n\t\treturn nil, 2\n\t}\n\tif !spend {\n\t\tfmt.Fprintln(stderr, \"fak deepseekbench: --live costs money — pass --spend to acknowledge (refusing)\")\n\t\treturn nil, 2\n\t}\n\trow, err := measureStreamed(baseURL, key, model)\n\tif err != nil {\n\t\tfmt.Fprintln(stderr, \"fak deepseekbench: live run failed:\", err)\n\t\treturn nil, 1\n\t}\n\treturn []DeepSeekBenchRow{row}, 0\n}\n\n// measureStreamed issues one streaming chat/completions request and times TTFT (first\n// content delta), TPOT (mean inter-delta), E2E, and reads the final usage block for the\n// token + prompt-cache counters. It is the untested-in-CI arm: it only runs behind the\n// key+spend gate, mirroring the #3013 self-host smoke that skips cleanly without a node.\nfunc measureStreamed(baseURL, key, model string) (DeepSeekBenchRow, error) {\n\tbody, _ := json.Marshal(map[string]any{\n\t\t\"model\": model,\n\t\t\"stream\": true,\n\t\t\"messages\": []map[string]string{{\"role\": \"user\", \"content\": \"Reply with a short sentence.\"}},\n\t\t\"stream_options\": map[string]any{\"include_usage\": true},\n\t})\n\turl := strings.TrimRight(baseURL, \"/\") + \"/chat/completions\"\n\treq, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))\n\tif err != nil {\n\t\treturn DeepSeekBenchRow{}, err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\treq.Header.Set(\"Authorization\", \"Bearer \"+key)\n\n\tclient := &http.Client{Timeout: 5 * time.Minute}\n\tstart := time.Now()\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn DeepSeekBenchRow{}, err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\tsnippet, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\n\t\treturn DeepSeekBenchRow{}, fmt.Errorf(\"upstream status %d: %s\", resp.StatusCode, strings.TrimSpace(string(snippet)))\n\t}\n\n\tvar ttft time.Duration\n\tvar deltas int\n\tvar lastDelta time.Time\n\tvar interSum time.Duration\n\tprompt, completion, reasoning, hit, miss := 0, 0, 0, 0, 0\n\tsc := bufio.NewScanner(resp.Body)\n\tsc.Buffer(make([]byte, 0, 64*1024), 4*1024*1024)\n\tfor sc.Scan() {\n\t\tline := strings.TrimSpace(sc.Text())\n\t\tif !strings.HasPrefix(line, \"data:\") {\n\t\t\tcontinue\n\t\t}\n\t\tdata := strings.TrimSpace(strings.TrimPrefix(line, \"data:\"))\n\t\tif data == \"[DONE]\" {\n\t\t\tbreak\n\t\t}\n\t\tvar ev struct {\n\t\t\tChoices []struct {\n\t\t\t\tDelta struct {\n\t\t\t\t\tContent string `json:\"content\"`\n\t\t\t\t\tReasoningContent string `json:\"reasoning_content\"`\n\t\t\t\t} `json:\"delta\"`\n\t\t\t} `json:\"choices\"`\n\t\t\tUsage *struct {\n\t\t\t\tPromptTokens int `json:\"prompt_tokens\"`\n\t\t\t\tCompletionTokens int `json:\"completion_tokens\"`\n\t\t\t\tReasoningTokens int `json:\"reasoning_tokens\"`\n\t\t\t\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\t\t\t\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\t\t\t} `json:\"usage\"`\n\t\t}\n\t\tif err := json.Unmarshal([]byte(data), &ev); err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif len(ev.Choices) > 0 && (ev.Choices[0].Delta.Content != \"\" || ev.Choices[0].Delta.ReasoningContent != \"\") {\n\t\t\tnow := time.Now()\n\t\t\tif ttft == 0 {\n\t\t\t\tttft = now.Sub(start)\n\t\t\t} else {\n\t\t\t\tinterSum += now.Sub(lastDelta)\n\t\t\t}\n\t\t\tlastDelta = now\n\t\t\tdeltas++\n\t\t}\n\t\tif ev.Usage != nil {\n\t\t\tprompt = ev.Usage.PromptTokens\n\t\t\tcompletion = ev.Usage.CompletionTokens\n\t\t\treasoning = ev.Usage.ReasoningTokens\n\t\t\thit = ev.Usage.PromptCacheHitTokens\n\t\t\tmiss = ev.Usage.PromptCacheMissTokens\n\t\t}\n\t}\n\tif err := sc.Err(); err != nil {\n\t\treturn DeepSeekBenchRow{}, err\n\t}\n\te2e := time.Since(start)\n\ttpot := 0.0\n\tif deltas > 1 {\n\t\ttpot = float64(interSum.Milliseconds()) / float64(deltas-1)\n\t}\n\ttoksPerSec := 0.0\n\tif e2e > 0 && completion > 0 {\n\t\ttoksPerSec = float64(completion) / e2e.Seconds()\n\t}\n\tattribution := \"unknown\"\n\tif hit > 0 || miss > 0 {\n\t\tattribution = \"provider-observed\"\n\t}\n\treturn DeepSeekBenchRow{\n\t\tMeasurement: \"live\",\n\t\tSpeedProvenance: \"provider-observed\",\n\t\tModelID: model,\n\t\tProviderRoute: \"deepseek\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: \"4K\",\n\t\tOutputTarget: \"short\",\n\t\tReasoningMode: \"non-thinking\",\n\t\tStream: true,\n\t\tTTFTMillis: round2(float64(ttft.Microseconds()) / 1000.0),\n\t\tTPOTMillis: round2(tpot),\n\t\tE2EMillis: round2(float64(e2e.Microseconds()) / 1000.0),\n\t\tOutputToksPerSec: round2(toksPerSec),\n\t\tPromptTokens: prompt,\n\t\tCompletionTokens: completion,\n\t\tReasoningTokens: reasoning,\n\t\tPromptCacheHitTokens: hit,\n\t\tPromptCacheMissTokens: miss,\n\t\tCacheAttribution: attribution,\n\t\tPromptShape: promptShape(\"4K\", \"short\", \"non-thinking\", true),\n\t\t// A single request cannot assert quality parity against another route.\n\t\tQualityParity: \"unknown\",\n\t}, nil\n}\n"}, "13f5f1a6e0391eb28bcbea759890fb009251e5d1": {"t": 1786067419.2390327, "n": 5337, "h": "39472957936720f5", "c": "package main\n\n// deepseekbench.go — `fak deepseekbench`, the thin WIRE over internal/deepseekbench:\n// the DeepSeek V4 Pro/Flash TTFT/TPOT/context-scaling SCORECARD (#3014, program\n// #3006; complements the self-host runbook #3013).\n//\n// The schema, dry-run fixture, speedup-refusal gate, and live measurement all live\n// in the pure, isolation-testable internal/deepseekbench package. This file is only\n// the flag/I-O shim: default is a NO-KEY DRY-RUN FIXTURE (JSONL rows → stdout,\n// coverage + honesty → stderr); a live run is opt-in and doubly gated behind\n// DEEPSEEK_API_KEY + an explicit --spend acknowledgement.\n//\n//\tfak deepseekbench # no-key dry-run fixture → JSONL\n//\tfak deepseekbench --out rows.jsonl # write the JSONL rows to a file\n//\tDEEPSEEK_API_KEY=… fak deepseekbench --live --spend --model deepseek-v4-pro\n//\tDEEPSEEK_API_KEY=… fak deepseekbench --live --spend \\\n//\t --base-url http://host:8000/v1 --model deepseek-ai/DeepSeek-V4-Pro # self-hosted arm\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/anthony-chaudhary/fak/internal/deepseekbench\"\n)\n\nfunc cmdDeepSeekBench(argv []string) { os.Exit(runDeepSeekBench(os.Stdout, os.Stderr, argv)) }\n\n// runDeepSeekBench is the testable core (exit code, no os.Exit): 0 ok, 1 a run error,\n// 2 a usage/gating error.\nfunc runDeepSeekBench(stdout, stderr io.Writer, argv []string) int {\n\tfs := flag.NewFlagSet(\"deepseekbench\", flag.ContinueOnError)\n\tfs.SetOutput(stderr)\n\tlive := fs.Bool(\"live\", false, \"opt-in LIVE run (requires DEEPSEEK_API_KEY and --spend); default is a no-key dry-run fixture\")\n\tspend := fs.Bool(\"spend\", false, \"explicit acknowledgement that a --live run costs money\")\n\tbaseURL := fs.String(\"base-url\", \"https://api.deepseek.com\", \"OpenAI-compatible root for a --live run (point at a self-hosted vLLM/SGLang endpoint for the self-hosted arm)\")\n\tmodel := fs.String(\"model\", deepseekbench.ModelV4Flash, \"model id to route for a --live run\")\n\toutPath := fs.String(\"out\", \"\", \"write the JSONL rows to this file (default: stdout)\")\n\tif rc, ok := parseFlagsOrHelp(fs, argv); !ok {\n\t\treturn rc\n\t}\n\n\tvar rows []deepseekbench.Row\n\tif *live {\n\t\tkey := strings.TrimSpace(os.Getenv(\"DEEPSEEK_API_KEY\"))\n\t\tif msg, ok := deepseekbench.LiveGate(key != \"\", *spend); !ok {\n\t\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", msg)\n\t\t\treturn 2\n\t\t}\n\t\trow, err := deepseekbench.MeasureStreamed(nil, *baseURL, key, *model)\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(stderr, \"fak deepseekbench: live run failed:\", err)\n\t\t\treturn 1\n\t\t}\n\t\trows = []deepseekbench.Row{row}\n\t} else {\n\t\trows = deepseekbench.DryRunRows()\n\t}\n\n\tsink := stdout\n\tif *outPath != \"\" {\n\t\tf, err := os.Create(*outPath)\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", err)\n\t\t\treturn 1\n\t\t}\n\t\tdefer f.Close()\n\t\tsink = f\n\t}\n\tif err := writeDeepSeekJSONL(sink, rows); err != nil {\n\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", err)\n\t\treturn 1\n\t}\n\n\tprintDeepSeekSummary(stderr, rows, *live)\n\treturn 0\n}\n\n// writeDeepSeekJSONL emits one compact JSON object per line — the locked scorecard format.\nfunc writeDeepSeekJSONL(w io.Writer, rows []deepseekbench.Row) error {\n\tbw := bufio.NewWriter(w)\n\tenc := json.NewEncoder(bw)\n\tfor _, r := range rows {\n\t\tif err := enc.Encode(r); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn bw.Flush()\n}\n\n// printDeepSeekSummary writes the human-facing coverage + honesty summary to stderr\n// (the JSONL rows themselves are the machine artifact on stdout/--out).\nfunc printDeepSeekSummary(w io.Writer, rows []deepseekbench.Row, live bool) {\n\tmodels := map[string]int{}\n\tfor _, r := range rows {\n\t\tmodels[r.ModelID]++\n\t}\n\tids := make([]string, 0, len(models))\n\tfor id := range models {\n\t\tids = append(ids, id)\n\t}\n\tsort.Strings(ids)\n\tfmt.Fprintln(w, \"== fak deepseekbench — DeepSeek V4 Pro/Flash TTFT/TPOT/context scorecard (#3014) ==\")\n\tif live {\n\t\tfmt.Fprintln(w, \"mode: LIVE (measurement=\\\"live\\\"; latency is provider-observed)\")\n\t} else {\n\t\tfmt.Fprintln(w, \"mode: DRY-RUN FIXTURE (measurement=\\\"dry-run-fixture\\\"; every latency is a labelled placeholder, NOT measured)\")\n\t}\n\tfmt.Fprintf(w, \"rows: %d across models:\\n\", len(rows))\n\tfor _, id := range ids {\n\t\tfmt.Fprintf(w, \" %-22s %d rows\\n\", id, models[id])\n\t}\n\t// Demonstrate the speedup-refusal gate on a DeepSeek row vs a non-DeepSeek baseline.\n\tfmt.Fprintf(w, \"scorecard speedup gate: %s\\n\", deepSeekGateExample(rows))\n\tfmt.Fprintln(w, \"OBSERVED provider speed is never a fak-authored saving — see docs/benchmarks/DEEPSEEK-V4-PERF-SCORECARD.md\")\n\tif !live {\n\t\tfmt.Fprintln(w, \"live recipe: DEEPSEEK_API_KEY=… fak deepseekbench --live --spend --model deepseek-v4-pro\")\n\t}\n}\n\n// deepSeekGateExample runs the refusal gate over the first DeepSeek row and the first\n// non-DeepSeek baseline row, so the summary always shows what the gate decides.\nfunc deepSeekGateExample(rows []deepseekbench.Row) string {\n\tvar subject, baseline *deepseekbench.Row\n\tfor i := range rows {\n\t\tif rows[i].ProviderRoute == \"deepseek\" && subject == nil {\n\t\t\tsubject = &rows[i]\n\t\t}\n\t\tif rows[i].ProviderRoute != \"deepseek\" && baseline == nil {\n\t\t\tbaseline = &rows[i]\n\t\t}\n\t}\n\tif subject == nil || baseline == nil {\n\t\treturn \"[NOT COMPARABLE: need one DeepSeek row and one baseline row]\"\n\t}\n\tline, _ := deepseekbench.CompareSpeedup(*subject, *baseline)\n\treturn line\n}\n"}, "c99f9d32ca81593f379f2688d676f9071cefe2df": {"t": 1786068284.6831596, "n": 5337, "h": "39472957936720f5", "c": "package main\n\n// deepseekbench.go — `fak deepseekbench`, the thin WIRE over internal/deepseekbench:\n// the DeepSeek V4 Pro/Flash TTFT/TPOT/context-scaling SCORECARD (#3014, program\n// #3006; complements the self-host runbook #3013).\n//\n// The schema, dry-run fixture, speedup-refusal gate, and live measurement all live\n// in the pure, isolation-testable internal/deepseekbench package. This file is only\n// the flag/I-O shim: default is a NO-KEY DRY-RUN FIXTURE (JSONL rows → stdout,\n// coverage + honesty → stderr); a live run is opt-in and doubly gated behind\n// DEEPSEEK_API_KEY + an explicit --spend acknowledgement.\n//\n//\tfak deepseekbench # no-key dry-run fixture → JSONL\n//\tfak deepseekbench --out rows.jsonl # write the JSONL rows to a file\n//\tDEEPSEEK_API_KEY=… fak deepseekbench --live --spend --model deepseek-v4-pro\n//\tDEEPSEEK_API_KEY=… fak deepseekbench --live --spend \\\n//\t --base-url http://host:8000/v1 --model deepseek-ai/DeepSeek-V4-Pro # self-hosted arm\n\nimport (\n\t\"bufio\"\n\t\"encoding/json\"\n\t\"flag\"\n\t\"fmt\"\n\t\"io\"\n\t\"os\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/anthony-chaudhary/fak/internal/deepseekbench\"\n)\n\nfunc cmdDeepSeekBench(argv []string) { os.Exit(runDeepSeekBench(os.Stdout, os.Stderr, argv)) }\n\n// runDeepSeekBench is the testable core (exit code, no os.Exit): 0 ok, 1 a run error,\n// 2 a usage/gating error.\nfunc runDeepSeekBench(stdout, stderr io.Writer, argv []string) int {\n\tfs := flag.NewFlagSet(\"deepseekbench\", flag.ContinueOnError)\n\tfs.SetOutput(stderr)\n\tlive := fs.Bool(\"live\", false, \"opt-in LIVE run (requires DEEPSEEK_API_KEY and --spend); default is a no-key dry-run fixture\")\n\tspend := fs.Bool(\"spend\", false, \"explicit acknowledgement that a --live run costs money\")\n\tbaseURL := fs.String(\"base-url\", \"https://api.deepseek.com\", \"OpenAI-compatible root for a --live run (point at a self-hosted vLLM/SGLang endpoint for the self-hosted arm)\")\n\tmodel := fs.String(\"model\", deepseekbench.ModelV4Flash, \"model id to route for a --live run\")\n\toutPath := fs.String(\"out\", \"\", \"write the JSONL rows to this file (default: stdout)\")\n\tif rc, ok := parseFlagsOrHelp(fs, argv); !ok {\n\t\treturn rc\n\t}\n\n\tvar rows []deepseekbench.Row\n\tif *live {\n\t\tkey := strings.TrimSpace(os.Getenv(\"DEEPSEEK_API_KEY\"))\n\t\tif msg, ok := deepseekbench.LiveGate(key != \"\", *spend); !ok {\n\t\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", msg)\n\t\t\treturn 2\n\t\t}\n\t\trow, err := deepseekbench.MeasureStreamed(nil, *baseURL, key, *model)\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(stderr, \"fak deepseekbench: live run failed:\", err)\n\t\t\treturn 1\n\t\t}\n\t\trows = []deepseekbench.Row{row}\n\t} else {\n\t\trows = deepseekbench.DryRunRows()\n\t}\n\n\tsink := stdout\n\tif *outPath != \"\" {\n\t\tf, err := os.Create(*outPath)\n\t\tif err != nil {\n\t\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", err)\n\t\t\treturn 1\n\t\t}\n\t\tdefer f.Close()\n\t\tsink = f\n\t}\n\tif err := writeDeepSeekJSONL(sink, rows); err != nil {\n\t\tfmt.Fprintln(stderr, \"fak deepseekbench:\", err)\n\t\treturn 1\n\t}\n\n\tprintDeepSeekSummary(stderr, rows, *live)\n\treturn 0\n}\n\n// writeDeepSeekJSONL emits one compact JSON object per line — the locked scorecard format.\nfunc writeDeepSeekJSONL(w io.Writer, rows []deepseekbench.Row) error {\n\tbw := bufio.NewWriter(w)\n\tenc := json.NewEncoder(bw)\n\tfor _, r := range rows {\n\t\tif err := enc.Encode(r); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn bw.Flush()\n}\n\n// printDeepSeekSummary writes the human-facing coverage + honesty summary to stderr\n// (the JSONL rows themselves are the machine artifact on stdout/--out).\nfunc printDeepSeekSummary(w io.Writer, rows []deepseekbench.Row, live bool) {\n\tmodels := map[string]int{}\n\tfor _, r := range rows {\n\t\tmodels[r.ModelID]++\n\t}\n\tids := make([]string, 0, len(models))\n\tfor id := range models {\n\t\tids = append(ids, id)\n\t}\n\tsort.Strings(ids)\n\tfmt.Fprintln(w, \"== fak deepseekbench — DeepSeek V4 Pro/Flash TTFT/TPOT/context scorecard (#3014) ==\")\n\tif live {\n\t\tfmt.Fprintln(w, \"mode: LIVE (measurement=\\\"live\\\"; latency is provider-observed)\")\n\t} else {\n\t\tfmt.Fprintln(w, \"mode: DRY-RUN FIXTURE (measurement=\\\"dry-run-fixture\\\"; every latency is a labelled placeholder, NOT measured)\")\n\t}\n\tfmt.Fprintf(w, \"rows: %d across models:\\n\", len(rows))\n\tfor _, id := range ids {\n\t\tfmt.Fprintf(w, \" %-22s %d rows\\n\", id, models[id])\n\t}\n\t// Demonstrate the speedup-refusal gate on a DeepSeek row vs a non-DeepSeek baseline.\n\tfmt.Fprintf(w, \"scorecard speedup gate: %s\\n\", deepSeekGateExample(rows))\n\tfmt.Fprintln(w, \"OBSERVED provider speed is never a fak-authored saving — see docs/benchmarks/DEEPSEEK-V4-PERF-SCORECARD.md\")\n\tif !live {\n\t\tfmt.Fprintln(w, \"live recipe: DEEPSEEK_API_KEY=… fak deepseekbench --live --spend --model deepseek-v4-pro\")\n\t}\n}\n\n// deepSeekGateExample runs the refusal gate over the first DeepSeek row and the first\n// non-DeepSeek baseline row, so the summary always shows what the gate decides.\nfunc deepSeekGateExample(rows []deepseekbench.Row) string {\n\tvar subject, baseline *deepseekbench.Row\n\tfor i := range rows {\n\t\tif rows[i].ProviderRoute == \"deepseek\" && subject == nil {\n\t\t\tsubject = &rows[i]\n\t\t}\n\t\tif rows[i].ProviderRoute != \"deepseek\" && baseline == nil {\n\t\t\tbaseline = &rows[i]\n\t\t}\n\t}\n\tif subject == nil || baseline == nil {\n\t\treturn \"[NOT COMPARABLE: need one DeepSeek row and one baseline row]\"\n\t}\n\tline, _ := deepseekbench.CompareSpeedup(*subject, *baseline)\n\treturn line\n}\n"}}, "internal/gateway/deepseek_anthropic.go": {"f1c36fd45ecfb60dc33288bccf3477b30bbd53cf": {"t": 1785745623.474223, "n": 22553, "h": "cde3b9b7dffdb8d0", "c": "package gateway\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/anthony-chaudhary/fak/internal/modelroute\"\n)\n\n// deepseek_anthropic.go — the DeepSeek ANTHROPIC-COMPATIBLE route profile and its\n// compatibility fences (#3010, under the DeepSeek V4 support program #3006).\n//\n// DeepSeek documents an Anthropic-compatible API at\n// https://api.deepseek.com/anthropic (source: https://api-docs.deepseek.com/guides/anthropic_api)\n// so a Claude Code-style client can front DeepSeek through the SAME `/anthropic`\n// base + `/v1/messages` composition fak already speaks to real Anthropic. This file\n// is the load-bearing wire that decides whether that fronting is SAFE — it does NOT\n// change the default Anthropic provider behavior for real Claude (that stays exactly\n// as internal/agent's anthropicAdapter composes it); it adds a DeepSeek profile on\n// top of the same composition and fences the documented compatibility GAPS so fak\n// fails loud instead of silently shipping a block the route drops.\n//\n// The three gaps this route MUST fence (all from the DeepSeek compatibility table,\n// https://api-docs.deepseek.com/guides/anthropic_api):\n// 1. Content blocks: images / documents / redacted-thinking / code-execution / MCP\n// blocks are NOT all supported. Sending one silently would lose data. The fence\n// refuses with a CLOSED reason (fail loud), never drops silently.\n// 2. `cache_control` is IGNORED by DeepSeek. So a cache_control breakpoint on this\n// route is a provider no-op, NOT a fak-managed prompt-cache saving — booking it\n// as one would misprice value accounting. IsFakManagedCacheSaving reports false.\n// 3. `thinking` is supported but `budget_tokens` is IGNORED. The request builder\n// enables thinking WITHOUT a budget so no caller is misled into thinking the\n// budget bound the response.\n//\n// PROVENANCE: this profile OBSERVES a third-party route. Everything it reports about\n// DeepSeek's behavior (ignored cache_control, model fallback) is PROVIDER-OBSERVED,\n// never fak-authored — the same OBSERVED-vs-WITNESSED discipline deepseek_pricing.go\n// applies to the billing counters.\n\n// DeepSeekAnthropicMessagesPath is the endpoint SUFFIX the Anthropic Messages wire\n// appends to a base URL. It MUST match internal/agent's anthropicAdapter.Endpoint\n// (`joinEndpoint(baseURL, \"/v1/messages\")`) so the DeepSeek route composes byte-for-byte\n// the way the real-Anthropic route does; deepseek_anthropic_test.go pins the parity.\nconst DeepSeekAnthropicMessagesPath = \"/v1/messages\"\n\n// DeepSeekAnthropicVersion is the anthropic-version header the DeepSeek route sends,\n// matching the value the real-Anthropic adapter uses. DeepSeek's Anthropic-compatible\n// endpoint accepts the same versioned Messages wire.\nconst DeepSeekAnthropicVersion = \"2023-06-01\"\n\n// closed-vocabulary reasons for a REFUSED unsupported content block. A refusal names\n// exactly one of these — never free text — so a caller (or a test) can branch on the\n// reason and an operator sees a stable, greppable token instead of prose.\nconst (\n\tDeepSeekAnthropicUnsupportedImage = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_IMAGE\"\n\tDeepSeekAnthropicUnsupportedDocument = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_DOCUMENT\"\n\tDeepSeekAnthropicUnsupportedRedactedThinking = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_REDACTED_THINKING\"\n\tDeepSeekAnthropicUnsupportedCodeExecution = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_CODE_EXECUTION\"\n\tDeepSeekAnthropicUnsupportedMCP = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_MCP\"\n)\n\n// deepSeekAnthropicUnsupportedBlocks maps each Anthropic-wire content-block `type`\n// DeepSeek does NOT support to its closed refusal reason. The keys are the documented\n// unsupported set (image, document, redacted thinking, code execution, MCP); a\n// server_tool_use block is DeepSeek's code-execution surface, and mcp_tool_use /\n// mcp_tool_result are the MCP content blocks. Everything NOT in this map is treated\n// as supported (text / tool_use / tool_result / thinking), so a genuinely new block\n// type fails OPEN as supported rather than being wrongly refused — the fence only\n// speaks about blocks the compatibility table names.\nvar deepSeekAnthropicUnsupportedBlocks = map[string]string{\n\t\"image\": DeepSeekAnthropicUnsupportedImage,\n\t\"document\": DeepSeekAnthropicUnsupportedDocument,\n\t\"redacted_thinking\": DeepSeekAnthropicUnsupportedRedactedThinking,\n\t\"code_execution\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\"server_tool_use\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\"mcp_tool_use\": DeepSeekAnthropicUnsupportedMCP,\n\t\"mcp_tool_result\": DeepSeekAnthropicUnsupportedMCP,\n}\n\n// DeepSeekAnthropicUnsupportedBlockReason reports whether a content-block `type` is\n// unsupported on the DeepSeek Anthropic route, and if so its closed refusal reason.\n// The type is normalized (trim + lower) so wire casing does not slip a block past the\n// fence.\nfunc DeepSeekAnthropicUnsupportedBlockReason(blockType string) (reason string, unsupported bool) {\n\tr, ok := deepSeekAnthropicUnsupportedBlocks[strings.ToLower(strings.TrimSpace(blockType))]\n\treturn r, ok\n}\n\n// DeepSeekAnthropicModelSource labels HOW a model id was resolved, so a ledger can tell\n// an operator-chosen direct id apart from a Claude-name mapping fak applied.\ntype DeepSeekAnthropicModelSource string\n\nconst (\n\t// DeepSeekAnthropicModelDirect: the caller already named a real DeepSeek V4 model\n\t// id (deepseek-v4-pro / deepseek-v4-flash). This is the PREFERRED form.\n\tDeepSeekAnthropicModelDirect DeepSeekAnthropicModelSource = \"direct\"\n\t// DeepSeekAnthropicModelClaudeMapped: the caller named a Claude family (opus /\n\t// sonnet / haiku) and fak applied its OWN explicit mapping. This is fak's policy,\n\t// NOT reliance on DeepSeek's server-side \"unknown model → Flash\" fallback.\n\tDeepSeekAnthropicModelClaudeMapped DeepSeekAnthropicModelSource = \"claude-name-mapped:deepseek-coding-agents-guide\"\n)\n\n// ResolveDeepSeekAnthropicModel turns the model id a Claude Code-style client sent into\n// the concrete DeepSeek V4 id fak will put on the wire. It PREFERS a direct\n// deepseek-v4-* id; for a Claude family name it applies fak's OWN explicit mapping\n// (Opus/Sonnet → Pro, Haiku → Flash) taken from DeepSeek's coding-agents guide\n// (https://api-docs.deepseek.com/guides/coding_agents), so the id on the wire is one\n// fak chose rather than leaving DeepSeek to silently coerce an unknown name to Flash.\n//\n// The DeepSeek Claude Code guide sets ANTHROPIC_MODEL=deepseek-v4-pro[1m]; the trailing\n// `[1m]` selects the 1M-context variant. It is stripped for the routing decision (both\n// variants map to the same base id) but reported via has1M so the caller can preserve\n// the marker on the wire if it wants the extended window.\n//\n// It FAILS LOUD (err != nil) on any other name rather than defaulting to Flash — the\n// issue is explicit that DeepSeek's unsupported-model fallback is NOT fak's routing\n// policy.\nfunc ResolveDeepSeekAnthropicModel(requested string) (model string, source DeepSeekAnthropicModelSource, has1M bool, err error) {\n\tnorm := strings.ToLower(strings.TrimSpace(requested))\n\tif strings.HasSuffix(norm, \"[1m]\") {\n\t\thas1M = true\n\t\tnorm = strings.TrimSpace(strings.TrimSuffix(norm, \"[1m]\"))\n\t}\n\tswitch norm {\n\tcase \"\":\n\t\treturn \"\", \"\", has1M, fmt.Errorf(\"deepseek-anthropic: empty model id; name deepseek-v4-pro or deepseek-v4-flash explicitly\")\n\tcase modelroute.DeepSeekV4ProModel:\n\t\treturn modelroute.DeepSeekV4ProModel, DeepSeekAnthropicModelDirect, has1M, nil\n\tcase modelroute.DeepSeekV4FlashModel:\n\t\treturn modelroute.DeepSeekV4FlashModel, DeepSeekAnthropicModelDirect, has1M, nil\n\t}\n\tswitch {\n\tcase strings.Contains(norm, \"opus\"), strings.Contains(norm, \"sonnet\"):\n\t\treturn modelroute.DeepSeekV4ProModel, DeepSeekAnthropicModelClaudeMapped, has1M, nil\n\tcase strings.Contains(norm, \"haiku\"):\n\t\treturn modelroute.DeepSeekV4FlashModel, DeepSeekAnthropicModelClaudeMapped, has1M, nil\n\t}\n\treturn \"\", \"\", has1M, fmt.Errorf(\"deepseek-anthropic: unroutable model %q; name deepseek-v4-pro/deepseek-v4-flash or a Claude opus/sonnet/haiku family (fak will not rely on DeepSeek's silent unknown-model→Flash fallback)\", requested)\n}\n\n// DeepSeek's Anthropic-compatible effort control (`output_config.effort`) accepts these\n// two levels for a coding agent; DeepSeek's guide recommends \"max\" for Claude Code.\nconst (\n\tDeepSeekAnthropicEffortHigh = \"high\"\n\tDeepSeekAnthropicEffortMax = \"max\"\n)\n\n// deepSeekAnthropicWireEfforts is the effort vocabulary this route can put on the wire —\n// the clamp target set for ResolveDeepSeekAnthropicEffort.\nvar deepSeekAnthropicWireEfforts = []string{DeepSeekAnthropicEffortHigh, DeepSeekAnthropicEffortMax}\n\n// ResolveDeepSeekAnthropicEffort maps a CANONICAL fak effort tier onto the two levels this\n// route speaks, SATURATING to the nearest supported tier rather than erroring a request the\n// provider can serve one rung away (#4069). fak's canonical ladder carries rungs DeepSeek\n// simply does not name (none/minimal/low/medium/xhigh), and refusing them made a caller's\n// perfectly ordinary `--effort low` fail the whole request; it now runs at high, and xhigh\n// runs at max (the tie breaks upward — see modelroute.SaturateEffort).\n//\n// The TYPO FENCE this replaced is preserved exactly where it earns its keep. Saturation is\n// only ever applied to a tier fak can PLACE on its ladder; a token off the ladder entirely\n// (\"extreem\") is still refused, because it carries no ordering to clamp with and is far\n// likelier a mistake than an intent. So the route degrades gracefully across a known\n// vocabulary gap while still failing loud on a genuinely unknown one.\n//\n// Returns (\"\", nil) for a blank effort: the control stays unset, which is not an error.\nfunc ResolveDeepSeekAnthropicEffort(effort string) (string, error) {\n\tif strings.TrimSpace(effort) == \"\" {\n\t\treturn \"\", nil\n\t}\n\tif tier := modelroute.SaturateEffort(effort, deepSeekAnthropicWireEfforts); tier != \"\" {\n\t\treturn tier, nil\n\t}\n\treturn \"\", fmt.Errorf(\"deepseek-anthropic: unknown output_config.effort %q; want a canonical effort tier (none/minimal/low/medium/high/xhigh/max), which is saturated to %q or %q\", effort, DeepSeekAnthropicEffortHigh, DeepSeekAnthropicEffortMax)\n}\n\n// ValidateDeepSeekAnthropicEffort reports whether an effort can reach the wire at all: nil\n// for a blank control and for any canonical tier (which ResolveDeepSeekAnthropicEffort\n// saturates into range), an error only for a tier off fak's ladder. Callers that need the\n// tier actually sent should use ResolveDeepSeekAnthropicEffort instead.\nfunc ValidateDeepSeekAnthropicEffort(effort string) error {\n\t_, err := ResolveDeepSeekAnthropicEffort(effort)\n\treturn err\n}\n\n// DeepSeekAnthropicCacheControlProvenance is the provenance label for a cache_control\n// breakpoint sent to the DeepSeek Anthropic route: DeepSeek IGNORES cache_control, so\n// it is a provider no-op, never a fak-authored saving.\nconst DeepSeekAnthropicCacheControlProvenance = \"provider-ignored\"\n\n// IsFakManagedCacheSaving reports whether a cache_control breakpoint on the DeepSeek\n// Anthropic route should be booked as a fak-managed prompt-cache SAVING. It is always\n// false: DeepSeek ignores cache_control, so any implied discount is provider-observed\n// economics (DeepSeek's own default context caching, priced by deepseek_pricing.go),\n// not something a fak mechanism shaped. This is the fence against reporting an ignored\n// cache_control as a fak cache effect.\nfunc IsFakManagedCacheSaving() bool { return false }\n\n// DeepSeekAnthropicProfile is a launch preset that fronts DeepSeek through the\n// Anthropic-compatible route. The zero value is not usable; use\n// NewDeepSeekAnthropicProfile so BaseURL defaults correctly.\ntype DeepSeekAnthropicProfile struct {\n\t// BaseURL is the Anthropic-compatible base; defaults to\n\t// modelroute.DeepSeekAnthropicBaseURL. The Messages path is appended by\n\t// MessagesURL, exactly as the real-Anthropic adapter composes it.\n\tBaseURL string\n\t// APIKey is the DeepSeek API key (the operator-owned DEEPSEEK_API_KEY secret). It\n\t// rides as x-api-key and is NEVER placed in the URL, model id, or body.\n\tAPIKey string\n\t// Model is the DeepSeek V4 id to put on the wire (already resolved via\n\t// ResolveDeepSeekAnthropicModel).\n\tModel string\n\t// Effort, if set, is emitted as output_config.effort. It holds a tier DeepSeek names\n\t// (\"high\"/\"max\"); NewDeepSeekAnthropicProfile has already saturated any other\n\t// canonical tier onto that set (#4069).\n\tEffort string\n}\n\n// NewDeepSeekAnthropicProfile builds a profile, defaulting BaseURL to DeepSeek's\n// documented Anthropic-compatible root and resolving the model id (failing loud on an\n// unroutable name). The [1m] extended-window marker, if the caller sent one, is\n// preserved on the wire model id.\nfunc NewDeepSeekAnthropicProfile(apiKey, requestedModel, effort string) (DeepSeekAnthropicProfile, error) {\n\tmodel, _, has1M, err := ResolveDeepSeekAnthropicModel(requestedModel)\n\tif err != nil {\n\t\treturn DeepSeekAnthropicProfile{}, err\n\t}\n\twireEffort, err := ResolveDeepSeekAnthropicEffort(effort)\n\tif err != nil {\n\t\treturn DeepSeekAnthropicProfile{}, err\n\t}\n\twireModel := model\n\tif has1M {\n\t\twireModel += \"[1m]\"\n\t}\n\treturn DeepSeekAnthropicProfile{\n\t\tBaseURL: modelroute.DeepSeekAnthropicBaseURL,\n\t\tAPIKey: apiKey,\n\t\tModel: wireModel,\n\t\tEffort: wireEffort,\n\t}, nil\n}\n\n// MessagesURL composes the concrete POST target the same way the real-Anthropic route\n// does: the base URL with a trailing slash trimmed, plus the Messages path. With the\n// default base that is https://api.deepseek.com/anthropic/v1/messages.\nfunc (p DeepSeekAnthropicProfile) MessagesURL() string {\n\tbase := p.BaseURL\n\tif base == \"\" {\n\t\tbase = modelroute.DeepSeekAnthropicBaseURL\n\t}\n\treturn strings.TrimRight(base, \"/\") + DeepSeekAnthropicMessagesPath\n}\n\n// headers builds the request headers. The API key rides ONLY as x-api-key (DeepSeek's\n// documented scheme for the Anthropic-compatible route); it never lands in the URL or\n// body, so a request/response log cannot leak it from those surfaces.\nfunc (p DeepSeekAnthropicProfile) headers() map[string]string {\n\th := map[string]string{\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"anthropic-version\": DeepSeekAnthropicVersion,\n\t}\n\tif p.APIKey != \"\" {\n\t\th[\"x-api-key\"] = p.APIKey\n\t}\n\treturn h\n}\n\n// DeepSeekAnthropicBlock is one Anthropic-wire content block on the REQUEST side. Only\n// the `type` is load-bearing for the fence; Text is carried for text blocks.\ntype DeepSeekAnthropicBlock struct {\n\tType string `json:\"type\"`\n\tText string `json:\"text,omitempty\"`\n}\n\n// DeepSeekAnthropicMessage is one role-tagged message with its content blocks.\ntype DeepSeekAnthropicMessage struct {\n\tRole string `json:\"role\"`\n\tContent []DeepSeekAnthropicBlock `json:\"content\"`\n}\n\n// FenceContentBlocks scans every request block and returns an error naming EVERY\n// unsupported block (deduped, sorted, each with its closed reason) so fak fails loud\n// rather than shipping an image/document/redacted-thinking/code-exec/MCP block to a\n// route that would silently drop it. Returns nil when every block is supported.\nfunc FenceContentBlocks(messages []DeepSeekAnthropicMessage) error {\n\tseen := map[string]bool{}\n\tvar refusals []string\n\tfor _, m := range messages {\n\t\tfor _, b := range m.Content {\n\t\t\tif reason, bad := DeepSeekAnthropicUnsupportedBlockReason(b.Type); bad {\n\t\t\t\tkey := strings.ToLower(strings.TrimSpace(b.Type)) + \" \" + reason\n\t\t\t\tif !seen[key] {\n\t\t\t\t\tseen[key] = true\n\t\t\t\t\trefusals = append(refusals, fmt.Sprintf(\"%s(%s)\", strings.ToLower(strings.TrimSpace(b.Type)), reason))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif len(refusals) == 0 {\n\t\treturn nil\n\t}\n\tsort.Strings(refusals)\n\treturn fmt.Errorf(\"deepseek-anthropic: unsupported content block(s) refused: %s\", strings.Join(refusals, \", \"))\n}\n\n// deepSeekAnthropicWireRequest is the JSON body posted to the Messages endpoint. It\n// enables thinking WITHOUT budget_tokens (DeepSeek ignores it) and emits\n// output_config.effort only when set.\ntype deepSeekAnthropicWireRequest struct {\n\tModel string `json:\"model\"`\n\tMaxTokens int `json:\"max_tokens\"`\n\tSystem string `json:\"system,omitempty\"`\n\tMessages []DeepSeekAnthropicMessage `json:\"messages\"`\n\tThinking *struct {\n\t\tType string `json:\"type\"`\n\t} `json:\"thinking,omitempty\"`\n\tOutputConfig *struct {\n\t\tEffort string `json:\"effort\"`\n\t} `json:\"output_config,omitempty\"`\n}\n\n// DeepSeekAnthropicResponseBlock is one block of the assistant response, covering the\n// text and thinking blocks DeepSeek returns (thinking is supported on this route).\ntype DeepSeekAnthropicResponseBlock struct {\n\tType string `json:\"type\"`\n\tText string `json:\"text,omitempty\"`\n\tThinking string `json:\"thinking,omitempty\"`\n\tSignature string `json:\"signature,omitempty\"`\n}\n\n// DeepSeekAnthropicResponse is the decoded Messages response. Usage carries DeepSeek's\n// own top-level cache hit/miss counters (the OBSERVED numbers deepseek_pricing.go\n// prices), NOT a fak-managed cache saving.\ntype DeepSeekAnthropicResponse struct {\n\tID string `json:\"id\"`\n\tModel string `json:\"model\"`\n\tRole string `json:\"role\"`\n\tContent []DeepSeekAnthropicResponseBlock `json:\"content\"`\n\tStopReason string `json:\"stop_reason\"`\n\tUsage struct {\n\t\tInputTokens int `json:\"input_tokens\"`\n\t\tOutputTokens int `json:\"output_tokens\"`\n\t\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\t\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\t} `json:\"usage\"`\n}\n\n// PostMessages builds the request (fencing unsupported blocks and validating effort),\n// posts it to MessagesURL with the x-api-key credential, and decodes the response. It\n// is the offline-testable seam: pass an *http.Client aimed at an httptest server to\n// exercise the full round trip without a network. thinking enables the thinking block;\n// budget_tokens is intentionally never sent (DeepSeek ignores it).\nfunc (p DeepSeekAnthropicProfile) PostMessages(ctx context.Context, client *http.Client, system string, messages []DeepSeekAnthropicMessage, maxTokens int, thinking bool) (*DeepSeekAnthropicResponse, error) {\n\tif strings.TrimSpace(p.Model) == \"\" {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: profile has no model; build it with NewDeepSeekAnthropicProfile\")\n\t}\n\t// Saturate here too, not just in the constructor: a profile can be hand-built as a\n\t// struct literal, and the tier that reaches the wire must be one DeepSeek names.\n\twireEffort, err := ResolveDeepSeekAnthropicEffort(p.Effort)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := FenceContentBlocks(messages); err != nil {\n\t\treturn nil, err\n\t}\n\tbody := deepSeekAnthropicWireRequest{\n\t\tModel: p.Model,\n\t\tMaxTokens: maxTokens,\n\t\tSystem: system,\n\t\tMessages: messages,\n\t}\n\tif thinking {\n\t\tbody.Thinking = &struct {\n\t\t\tType string `json:\"type\"`\n\t\t}{Type: \"enabled\"}\n\t}\n\tif wireEffort != \"\" {\n\t\tbody.OutputConfig = &struct {\n\t\t\tEffort string `json:\"effort\"`\n\t\t}{Effort: wireEffort}\n\t}\n\traw, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: marshal request: %w\", err)\n\t}\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, p.MessagesURL(), bytes.NewReader(raw))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: build request: %w\", err)\n\t}\n\tfor k, v := range p.headers() {\n\t\treq.Header.Set(k, v)\n\t}\n\tif client == nil {\n\t\tclient = &http.Client{Timeout: durEnv(\"FAK_DEEPSEEK_ANTHROPIC_TIMEOUT_S\", 120*time.Second)}\n\t}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: post: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\tpayload, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: read response: %w\", err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: upstream status %d: %s\", resp.StatusCode, strings.TrimSpace(string(payload)))\n\t}\n\tvar out DeepSeekAnthropicResponse\n\tif err := json.Unmarshal(payload, &out); err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: decode response: %w\", err)\n\t}\n\treturn &out, nil\n}\n\n// ClaudeCodeDeepSeekEnv is the machine-readable source for the Claude Code runbook\n// (rendered by the human runbook, #3012): the environment a Claude Code-style client\n// sets to route through fak into DeepSeek. The API-key entry is the NAME of the\n// operator-owned secret env var (DEEPSEEK_API_KEY), never a literal secret, so the\n// preset can be printed or committed without leaking a credential. requestedModel is\n// resolved (and validated) so an unroutable name is caught before it reaches a runbook.\nfunc ClaudeCodeDeepSeekEnv(requestedModel string) (map[string]string, error) {\n\tprof, err := NewDeepSeekAnthropicProfile(\"\", requestedModel, \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn map[string]string{\n\t\t\"ANTHROPIC_BASE_URL\": modelroute.DeepSeekAnthropicBaseURL,\n\t\t\"ANTHROPIC_MODEL\": prof.Model,\n\t\t\"ANTHROPIC_API_KEY\": \"$\" + modelroute.DeepSeekAPIKeyEnv,\n\t\t\"ANTHROPIC_AUTH_KEY\": modelroute.DeepSeekAPIKeyEnv,\n\t}, nil\n}\n\n// ClaudeCodeDeepSeekRunbook renders the env preset as copy-pasteable shell lines for\n// one platform: \"unix\" (bash/zsh `export`) or \"windows\" (PowerShell `$env:`). Both\n// reference the operator-owned DEEPSEEK_API_KEY by name and never inline a secret.\nfunc ClaudeCodeDeepSeekRunbook(platform, requestedModel string) ([]string, error) {\n\tenv, err := ClaudeCodeDeepSeekEnv(requestedModel)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkeys := make([]string, 0, len(env))\n\tfor k := range env {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\tvar lines []string\n\tswitch strings.ToLower(strings.TrimSpace(platform)) {\n\tcase \"unix\", \"linux\", \"macos\", \"darwin\":\n\t\tfor _, k := range keys {\n\t\t\tlines = append(lines, fmt.Sprintf(\"export %s=%s\", k, env[k]))\n\t\t}\n\tcase \"windows\", \"powershell\":\n\t\tfor _, k := range keys {\n\t\t\t// PowerShell reads a shell var with $env:NAME; a $-prefixed value from the\n\t\t\t// preset (the secret reference) is rewritten to PowerShell's own form.\n\t\t\tval := env[k]\n\t\t\tif strings.HasPrefix(val, \"$\") {\n\t\t\t\tval = \"$env:\" + strings.TrimPrefix(val, \"$\")\n\t\t\t}\n\t\t\tlines = append(lines, fmt.Sprintf(\"$env:%s = \\\"%s\\\"\", k, val))\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: unknown runbook platform %q; want \\\"unix\\\" or \\\"windows\\\"\", platform)\n\t}\n\treturn lines, nil\n}\n"}, "6306acd745190fccfc6905301852597809367fed": {"t": 1785749570.3958335, "n": 22553, "h": "cde3b9b7dffdb8d0", "c": "package gateway\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/anthony-chaudhary/fak/internal/modelroute\"\n)\n\n// deepseek_anthropic.go — the DeepSeek ANTHROPIC-COMPATIBLE route profile and its\n// compatibility fences (#3010, under the DeepSeek V4 support program #3006).\n//\n// DeepSeek documents an Anthropic-compatible API at\n// https://api.deepseek.com/anthropic (source: https://api-docs.deepseek.com/guides/anthropic_api)\n// so a Claude Code-style client can front DeepSeek through the SAME `/anthropic`\n// base + `/v1/messages` composition fak already speaks to real Anthropic. This file\n// is the load-bearing wire that decides whether that fronting is SAFE — it does NOT\n// change the default Anthropic provider behavior for real Claude (that stays exactly\n// as internal/agent's anthropicAdapter composes it); it adds a DeepSeek profile on\n// top of the same composition and fences the documented compatibility GAPS so fak\n// fails loud instead of silently shipping a block the route drops.\n//\n// The three gaps this route MUST fence (all from the DeepSeek compatibility table,\n// https://api-docs.deepseek.com/guides/anthropic_api):\n// 1. Content blocks: images / documents / redacted-thinking / code-execution / MCP\n// blocks are NOT all supported. Sending one silently would lose data. The fence\n// refuses with a CLOSED reason (fail loud), never drops silently.\n// 2. `cache_control` is IGNORED by DeepSeek. So a cache_control breakpoint on this\n// route is a provider no-op, NOT a fak-managed prompt-cache saving — booking it\n// as one would misprice value accounting. IsFakManagedCacheSaving reports false.\n// 3. `thinking` is supported but `budget_tokens` is IGNORED. The request builder\n// enables thinking WITHOUT a budget so no caller is misled into thinking the\n// budget bound the response.\n//\n// PROVENANCE: this profile OBSERVES a third-party route. Everything it reports about\n// DeepSeek's behavior (ignored cache_control, model fallback) is PROVIDER-OBSERVED,\n// never fak-authored — the same OBSERVED-vs-WITNESSED discipline deepseek_pricing.go\n// applies to the billing counters.\n\n// DeepSeekAnthropicMessagesPath is the endpoint SUFFIX the Anthropic Messages wire\n// appends to a base URL. It MUST match internal/agent's anthropicAdapter.Endpoint\n// (`joinEndpoint(baseURL, \"/v1/messages\")`) so the DeepSeek route composes byte-for-byte\n// the way the real-Anthropic route does; deepseek_anthropic_test.go pins the parity.\nconst DeepSeekAnthropicMessagesPath = \"/v1/messages\"\n\n// DeepSeekAnthropicVersion is the anthropic-version header the DeepSeek route sends,\n// matching the value the real-Anthropic adapter uses. DeepSeek's Anthropic-compatible\n// endpoint accepts the same versioned Messages wire.\nconst DeepSeekAnthropicVersion = \"2023-06-01\"\n\n// closed-vocabulary reasons for a REFUSED unsupported content block. A refusal names\n// exactly one of these — never free text — so a caller (or a test) can branch on the\n// reason and an operator sees a stable, greppable token instead of prose.\nconst (\n\tDeepSeekAnthropicUnsupportedImage = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_IMAGE\"\n\tDeepSeekAnthropicUnsupportedDocument = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_DOCUMENT\"\n\tDeepSeekAnthropicUnsupportedRedactedThinking = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_REDACTED_THINKING\"\n\tDeepSeekAnthropicUnsupportedCodeExecution = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_CODE_EXECUTION\"\n\tDeepSeekAnthropicUnsupportedMCP = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_MCP\"\n)\n\n// deepSeekAnthropicUnsupportedBlocks maps each Anthropic-wire content-block `type`\n// DeepSeek does NOT support to its closed refusal reason. The keys are the documented\n// unsupported set (image, document, redacted thinking, code execution, MCP); a\n// server_tool_use block is DeepSeek's code-execution surface, and mcp_tool_use /\n// mcp_tool_result are the MCP content blocks. Everything NOT in this map is treated\n// as supported (text / tool_use / tool_result / thinking), so a genuinely new block\n// type fails OPEN as supported rather than being wrongly refused — the fence only\n// speaks about blocks the compatibility table names.\nvar deepSeekAnthropicUnsupportedBlocks = map[string]string{\n\t\"image\": DeepSeekAnthropicUnsupportedImage,\n\t\"document\": DeepSeekAnthropicUnsupportedDocument,\n\t\"redacted_thinking\": DeepSeekAnthropicUnsupportedRedactedThinking,\n\t\"code_execution\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\"server_tool_use\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\"mcp_tool_use\": DeepSeekAnthropicUnsupportedMCP,\n\t\"mcp_tool_result\": DeepSeekAnthropicUnsupportedMCP,\n}\n\n// DeepSeekAnthropicUnsupportedBlockReason reports whether a content-block `type` is\n// unsupported on the DeepSeek Anthropic route, and if so its closed refusal reason.\n// The type is normalized (trim + lower) so wire casing does not slip a block past the\n// fence.\nfunc DeepSeekAnthropicUnsupportedBlockReason(blockType string) (reason string, unsupported bool) {\n\tr, ok := deepSeekAnthropicUnsupportedBlocks[strings.ToLower(strings.TrimSpace(blockType))]\n\treturn r, ok\n}\n\n// DeepSeekAnthropicModelSource labels HOW a model id was resolved, so a ledger can tell\n// an operator-chosen direct id apart from a Claude-name mapping fak applied.\ntype DeepSeekAnthropicModelSource string\n\nconst (\n\t// DeepSeekAnthropicModelDirect: the caller already named a real DeepSeek V4 model\n\t// id (deepseek-v4-pro / deepseek-v4-flash). This is the PREFERRED form.\n\tDeepSeekAnthropicModelDirect DeepSeekAnthropicModelSource = \"direct\"\n\t// DeepSeekAnthropicModelClaudeMapped: the caller named a Claude family (opus /\n\t// sonnet / haiku) and fak applied its OWN explicit mapping. This is fak's policy,\n\t// NOT reliance on DeepSeek's server-side \"unknown model → Flash\" fallback.\n\tDeepSeekAnthropicModelClaudeMapped DeepSeekAnthropicModelSource = \"claude-name-mapped:deepseek-coding-agents-guide\"\n)\n\n// ResolveDeepSeekAnthropicModel turns the model id a Claude Code-style client sent into\n// the concrete DeepSeek V4 id fak will put on the wire. It PREFERS a direct\n// deepseek-v4-* id; for a Claude family name it applies fak's OWN explicit mapping\n// (Opus/Sonnet → Pro, Haiku → Flash) taken from DeepSeek's coding-agents guide\n// (https://api-docs.deepseek.com/guides/coding_agents), so the id on the wire is one\n// fak chose rather than leaving DeepSeek to silently coerce an unknown name to Flash.\n//\n// The DeepSeek Claude Code guide sets ANTHROPIC_MODEL=deepseek-v4-pro[1m]; the trailing\n// `[1m]` selects the 1M-context variant. It is stripped for the routing decision (both\n// variants map to the same base id) but reported via has1M so the caller can preserve\n// the marker on the wire if it wants the extended window.\n//\n// It FAILS LOUD (err != nil) on any other name rather than defaulting to Flash — the\n// issue is explicit that DeepSeek's unsupported-model fallback is NOT fak's routing\n// policy.\nfunc ResolveDeepSeekAnthropicModel(requested string) (model string, source DeepSeekAnthropicModelSource, has1M bool, err error) {\n\tnorm := strings.ToLower(strings.TrimSpace(requested))\n\tif strings.HasSuffix(norm, \"[1m]\") {\n\t\thas1M = true\n\t\tnorm = strings.TrimSpace(strings.TrimSuffix(norm, \"[1m]\"))\n\t}\n\tswitch norm {\n\tcase \"\":\n\t\treturn \"\", \"\", has1M, fmt.Errorf(\"deepseek-anthropic: empty model id; name deepseek-v4-pro or deepseek-v4-flash explicitly\")\n\tcase modelroute.DeepSeekV4ProModel:\n\t\treturn modelroute.DeepSeekV4ProModel, DeepSeekAnthropicModelDirect, has1M, nil\n\tcase modelroute.DeepSeekV4FlashModel:\n\t\treturn modelroute.DeepSeekV4FlashModel, DeepSeekAnthropicModelDirect, has1M, nil\n\t}\n\tswitch {\n\tcase strings.Contains(norm, \"opus\"), strings.Contains(norm, \"sonnet\"):\n\t\treturn modelroute.DeepSeekV4ProModel, DeepSeekAnthropicModelClaudeMapped, has1M, nil\n\tcase strings.Contains(norm, \"haiku\"):\n\t\treturn modelroute.DeepSeekV4FlashModel, DeepSeekAnthropicModelClaudeMapped, has1M, nil\n\t}\n\treturn \"\", \"\", has1M, fmt.Errorf(\"deepseek-anthropic: unroutable model %q; name deepseek-v4-pro/deepseek-v4-flash or a Claude opus/sonnet/haiku family (fak will not rely on DeepSeek's silent unknown-model→Flash fallback)\", requested)\n}\n\n// DeepSeek's Anthropic-compatible effort control (`output_config.effort`) accepts these\n// two levels for a coding agent; DeepSeek's guide recommends \"max\" for Claude Code.\nconst (\n\tDeepSeekAnthropicEffortHigh = \"high\"\n\tDeepSeekAnthropicEffortMax = \"max\"\n)\n\n// deepSeekAnthropicWireEfforts is the effort vocabulary this route can put on the wire —\n// the clamp target set for ResolveDeepSeekAnthropicEffort.\nvar deepSeekAnthropicWireEfforts = []string{DeepSeekAnthropicEffortHigh, DeepSeekAnthropicEffortMax}\n\n// ResolveDeepSeekAnthropicEffort maps a CANONICAL fak effort tier onto the two levels this\n// route speaks, SATURATING to the nearest supported tier rather than erroring a request the\n// provider can serve one rung away (#4069). fak's canonical ladder carries rungs DeepSeek\n// simply does not name (none/minimal/low/medium/xhigh), and refusing them made a caller's\n// perfectly ordinary `--effort low` fail the whole request; it now runs at high, and xhigh\n// runs at max (the tie breaks upward — see modelroute.SaturateEffort).\n//\n// The TYPO FENCE this replaced is preserved exactly where it earns its keep. Saturation is\n// only ever applied to a tier fak can PLACE on its ladder; a token off the ladder entirely\n// (\"extreem\") is still refused, because it carries no ordering to clamp with and is far\n// likelier a mistake than an intent. So the route degrades gracefully across a known\n// vocabulary gap while still failing loud on a genuinely unknown one.\n//\n// Returns (\"\", nil) for a blank effort: the control stays unset, which is not an error.\nfunc ResolveDeepSeekAnthropicEffort(effort string) (string, error) {\n\tif strings.TrimSpace(effort) == \"\" {\n\t\treturn \"\", nil\n\t}\n\tif tier := modelroute.SaturateEffort(effort, deepSeekAnthropicWireEfforts); tier != \"\" {\n\t\treturn tier, nil\n\t}\n\treturn \"\", fmt.Errorf(\"deepseek-anthropic: unknown output_config.effort %q; want a canonical effort tier (none/minimal/low/medium/high/xhigh/max), which is saturated to %q or %q\", effort, DeepSeekAnthropicEffortHigh, DeepSeekAnthropicEffortMax)\n}\n\n// ValidateDeepSeekAnthropicEffort reports whether an effort can reach the wire at all: nil\n// for a blank control and for any canonical tier (which ResolveDeepSeekAnthropicEffort\n// saturates into range), an error only for a tier off fak's ladder. Callers that need the\n// tier actually sent should use ResolveDeepSeekAnthropicEffort instead.\nfunc ValidateDeepSeekAnthropicEffort(effort string) error {\n\t_, err := ResolveDeepSeekAnthropicEffort(effort)\n\treturn err\n}\n\n// DeepSeekAnthropicCacheControlProvenance is the provenance label for a cache_control\n// breakpoint sent to the DeepSeek Anthropic route: DeepSeek IGNORES cache_control, so\n// it is a provider no-op, never a fak-authored saving.\nconst DeepSeekAnthropicCacheControlProvenance = \"provider-ignored\"\n\n// IsFakManagedCacheSaving reports whether a cache_control breakpoint on the DeepSeek\n// Anthropic route should be booked as a fak-managed prompt-cache SAVING. It is always\n// false: DeepSeek ignores cache_control, so any implied discount is provider-observed\n// economics (DeepSeek's own default context caching, priced by deepseek_pricing.go),\n// not something a fak mechanism shaped. This is the fence against reporting an ignored\n// cache_control as a fak cache effect.\nfunc IsFakManagedCacheSaving() bool { return false }\n\n// DeepSeekAnthropicProfile is a launch preset that fronts DeepSeek through the\n// Anthropic-compatible route. The zero value is not usable; use\n// NewDeepSeekAnthropicProfile so BaseURL defaults correctly.\ntype DeepSeekAnthropicProfile struct {\n\t// BaseURL is the Anthropic-compatible base; defaults to\n\t// modelroute.DeepSeekAnthropicBaseURL. The Messages path is appended by\n\t// MessagesURL, exactly as the real-Anthropic adapter composes it.\n\tBaseURL string\n\t// APIKey is the DeepSeek API key (the operator-owned DEEPSEEK_API_KEY secret). It\n\t// rides as x-api-key and is NEVER placed in the URL, model id, or body.\n\tAPIKey string\n\t// Model is the DeepSeek V4 id to put on the wire (already resolved via\n\t// ResolveDeepSeekAnthropicModel).\n\tModel string\n\t// Effort, if set, is emitted as output_config.effort. It holds a tier DeepSeek names\n\t// (\"high\"/\"max\"); NewDeepSeekAnthropicProfile has already saturated any other\n\t// canonical tier onto that set (#4069).\n\tEffort string\n}\n\n// NewDeepSeekAnthropicProfile builds a profile, defaulting BaseURL to DeepSeek's\n// documented Anthropic-compatible root and resolving the model id (failing loud on an\n// unroutable name). The [1m] extended-window marker, if the caller sent one, is\n// preserved on the wire model id.\nfunc NewDeepSeekAnthropicProfile(apiKey, requestedModel, effort string) (DeepSeekAnthropicProfile, error) {\n\tmodel, _, has1M, err := ResolveDeepSeekAnthropicModel(requestedModel)\n\tif err != nil {\n\t\treturn DeepSeekAnthropicProfile{}, err\n\t}\n\twireEffort, err := ResolveDeepSeekAnthropicEffort(effort)\n\tif err != nil {\n\t\treturn DeepSeekAnthropicProfile{}, err\n\t}\n\twireModel := model\n\tif has1M {\n\t\twireModel += \"[1m]\"\n\t}\n\treturn DeepSeekAnthropicProfile{\n\t\tBaseURL: modelroute.DeepSeekAnthropicBaseURL,\n\t\tAPIKey: apiKey,\n\t\tModel: wireModel,\n\t\tEffort: wireEffort,\n\t}, nil\n}\n\n// MessagesURL composes the concrete POST target the same way the real-Anthropic route\n// does: the base URL with a trailing slash trimmed, plus the Messages path. With the\n// default base that is https://api.deepseek.com/anthropic/v1/messages.\nfunc (p DeepSeekAnthropicProfile) MessagesURL() string {\n\tbase := p.BaseURL\n\tif base == \"\" {\n\t\tbase = modelroute.DeepSeekAnthropicBaseURL\n\t}\n\treturn strings.TrimRight(base, \"/\") + DeepSeekAnthropicMessagesPath\n}\n\n// headers builds the request headers. The API key rides ONLY as x-api-key (DeepSeek's\n// documented scheme for the Anthropic-compatible route); it never lands in the URL or\n// body, so a request/response log cannot leak it from those surfaces.\nfunc (p DeepSeekAnthropicProfile) headers() map[string]string {\n\th := map[string]string{\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"anthropic-version\": DeepSeekAnthropicVersion,\n\t}\n\tif p.APIKey != \"\" {\n\t\th[\"x-api-key\"] = p.APIKey\n\t}\n\treturn h\n}\n\n// DeepSeekAnthropicBlock is one Anthropic-wire content block on the REQUEST side. Only\n// the `type` is load-bearing for the fence; Text is carried for text blocks.\ntype DeepSeekAnthropicBlock struct {\n\tType string `json:\"type\"`\n\tText string `json:\"text,omitempty\"`\n}\n\n// DeepSeekAnthropicMessage is one role-tagged message with its content blocks.\ntype DeepSeekAnthropicMessage struct {\n\tRole string `json:\"role\"`\n\tContent []DeepSeekAnthropicBlock `json:\"content\"`\n}\n\n// FenceContentBlocks scans every request block and returns an error naming EVERY\n// unsupported block (deduped, sorted, each with its closed reason) so fak fails loud\n// rather than shipping an image/document/redacted-thinking/code-exec/MCP block to a\n// route that would silently drop it. Returns nil when every block is supported.\nfunc FenceContentBlocks(messages []DeepSeekAnthropicMessage) error {\n\tseen := map[string]bool{}\n\tvar refusals []string\n\tfor _, m := range messages {\n\t\tfor _, b := range m.Content {\n\t\t\tif reason, bad := DeepSeekAnthropicUnsupportedBlockReason(b.Type); bad {\n\t\t\t\tkey := strings.ToLower(strings.TrimSpace(b.Type)) + \" \" + reason\n\t\t\t\tif !seen[key] {\n\t\t\t\t\tseen[key] = true\n\t\t\t\t\trefusals = append(refusals, fmt.Sprintf(\"%s(%s)\", strings.ToLower(strings.TrimSpace(b.Type)), reason))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif len(refusals) == 0 {\n\t\treturn nil\n\t}\n\tsort.Strings(refusals)\n\treturn fmt.Errorf(\"deepseek-anthropic: unsupported content block(s) refused: %s\", strings.Join(refusals, \", \"))\n}\n\n// deepSeekAnthropicWireRequest is the JSON body posted to the Messages endpoint. It\n// enables thinking WITHOUT budget_tokens (DeepSeek ignores it) and emits\n// output_config.effort only when set.\ntype deepSeekAnthropicWireRequest struct {\n\tModel string `json:\"model\"`\n\tMaxTokens int `json:\"max_tokens\"`\n\tSystem string `json:\"system,omitempty\"`\n\tMessages []DeepSeekAnthropicMessage `json:\"messages\"`\n\tThinking *struct {\n\t\tType string `json:\"type\"`\n\t} `json:\"thinking,omitempty\"`\n\tOutputConfig *struct {\n\t\tEffort string `json:\"effort\"`\n\t} `json:\"output_config,omitempty\"`\n}\n\n// DeepSeekAnthropicResponseBlock is one block of the assistant response, covering the\n// text and thinking blocks DeepSeek returns (thinking is supported on this route).\ntype DeepSeekAnthropicResponseBlock struct {\n\tType string `json:\"type\"`\n\tText string `json:\"text,omitempty\"`\n\tThinking string `json:\"thinking,omitempty\"`\n\tSignature string `json:\"signature,omitempty\"`\n}\n\n// DeepSeekAnthropicResponse is the decoded Messages response. Usage carries DeepSeek's\n// own top-level cache hit/miss counters (the OBSERVED numbers deepseek_pricing.go\n// prices), NOT a fak-managed cache saving.\ntype DeepSeekAnthropicResponse struct {\n\tID string `json:\"id\"`\n\tModel string `json:\"model\"`\n\tRole string `json:\"role\"`\n\tContent []DeepSeekAnthropicResponseBlock `json:\"content\"`\n\tStopReason string `json:\"stop_reason\"`\n\tUsage struct {\n\t\tInputTokens int `json:\"input_tokens\"`\n\t\tOutputTokens int `json:\"output_tokens\"`\n\t\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\t\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\t} `json:\"usage\"`\n}\n\n// PostMessages builds the request (fencing unsupported blocks and validating effort),\n// posts it to MessagesURL with the x-api-key credential, and decodes the response. It\n// is the offline-testable seam: pass an *http.Client aimed at an httptest server to\n// exercise the full round trip without a network. thinking enables the thinking block;\n// budget_tokens is intentionally never sent (DeepSeek ignores it).\nfunc (p DeepSeekAnthropicProfile) PostMessages(ctx context.Context, client *http.Client, system string, messages []DeepSeekAnthropicMessage, maxTokens int, thinking bool) (*DeepSeekAnthropicResponse, error) {\n\tif strings.TrimSpace(p.Model) == \"\" {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: profile has no model; build it with NewDeepSeekAnthropicProfile\")\n\t}\n\t// Saturate here too, not just in the constructor: a profile can be hand-built as a\n\t// struct literal, and the tier that reaches the wire must be one DeepSeek names.\n\twireEffort, err := ResolveDeepSeekAnthropicEffort(p.Effort)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := FenceContentBlocks(messages); err != nil {\n\t\treturn nil, err\n\t}\n\tbody := deepSeekAnthropicWireRequest{\n\t\tModel: p.Model,\n\t\tMaxTokens: maxTokens,\n\t\tSystem: system,\n\t\tMessages: messages,\n\t}\n\tif thinking {\n\t\tbody.Thinking = &struct {\n\t\t\tType string `json:\"type\"`\n\t\t}{Type: \"enabled\"}\n\t}\n\tif wireEffort != \"\" {\n\t\tbody.OutputConfig = &struct {\n\t\t\tEffort string `json:\"effort\"`\n\t\t}{Effort: wireEffort}\n\t}\n\traw, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: marshal request: %w\", err)\n\t}\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, p.MessagesURL(), bytes.NewReader(raw))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: build request: %w\", err)\n\t}\n\tfor k, v := range p.headers() {\n\t\treq.Header.Set(k, v)\n\t}\n\tif client == nil {\n\t\tclient = &http.Client{Timeout: durEnv(\"FAK_DEEPSEEK_ANTHROPIC_TIMEOUT_S\", 120*time.Second)}\n\t}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: post: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\tpayload, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: read response: %w\", err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: upstream status %d: %s\", resp.StatusCode, strings.TrimSpace(string(payload)))\n\t}\n\tvar out DeepSeekAnthropicResponse\n\tif err := json.Unmarshal(payload, &out); err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: decode response: %w\", err)\n\t}\n\treturn &out, nil\n}\n\n// ClaudeCodeDeepSeekEnv is the machine-readable source for the Claude Code runbook\n// (rendered by the human runbook, #3012): the environment a Claude Code-style client\n// sets to route through fak into DeepSeek. The API-key entry is the NAME of the\n// operator-owned secret env var (DEEPSEEK_API_KEY), never a literal secret, so the\n// preset can be printed or committed without leaking a credential. requestedModel is\n// resolved (and validated) so an unroutable name is caught before it reaches a runbook.\nfunc ClaudeCodeDeepSeekEnv(requestedModel string) (map[string]string, error) {\n\tprof, err := NewDeepSeekAnthropicProfile(\"\", requestedModel, \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn map[string]string{\n\t\t\"ANTHROPIC_BASE_URL\": modelroute.DeepSeekAnthropicBaseURL,\n\t\t\"ANTHROPIC_MODEL\": prof.Model,\n\t\t\"ANTHROPIC_API_KEY\": \"$\" + modelroute.DeepSeekAPIKeyEnv,\n\t\t\"ANTHROPIC_AUTH_KEY\": modelroute.DeepSeekAPIKeyEnv,\n\t}, nil\n}\n\n// ClaudeCodeDeepSeekRunbook renders the env preset as copy-pasteable shell lines for\n// one platform: \"unix\" (bash/zsh `export`) or \"windows\" (PowerShell `$env:`). Both\n// reference the operator-owned DEEPSEEK_API_KEY by name and never inline a secret.\nfunc ClaudeCodeDeepSeekRunbook(platform, requestedModel string) ([]string, error) {\n\tenv, err := ClaudeCodeDeepSeekEnv(requestedModel)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkeys := make([]string, 0, len(env))\n\tfor k := range env {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\tvar lines []string\n\tswitch strings.ToLower(strings.TrimSpace(platform)) {\n\tcase \"unix\", \"linux\", \"macos\", \"darwin\":\n\t\tfor _, k := range keys {\n\t\t\tlines = append(lines, fmt.Sprintf(\"export %s=%s\", k, env[k]))\n\t\t}\n\tcase \"windows\", \"powershell\":\n\t\tfor _, k := range keys {\n\t\t\t// PowerShell reads a shell var with $env:NAME; a $-prefixed value from the\n\t\t\t// preset (the secret reference) is rewritten to PowerShell's own form.\n\t\t\tval := env[k]\n\t\t\tif strings.HasPrefix(val, \"$\") {\n\t\t\t\tval = \"$env:\" + strings.TrimPrefix(val, \"$\")\n\t\t\t}\n\t\t\tlines = append(lines, fmt.Sprintf(\"$env:%s = \\\"%s\\\"\", k, val))\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: unknown runbook platform %q; want \\\"unix\\\" or \\\"windows\\\"\", platform)\n\t}\n\treturn lines, nil\n}\n"}, "138a286b777d30cb32a93e2371a3fd6444fabfcb": {"t": 1785749571.647751, "n": 20549, "h": "7d54bb08cdeaea18", "c": "package gateway\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/anthony-chaudhary/fak/internal/modelroute\"\n)\n\n// deepseek_anthropic.go — the DeepSeek ANTHROPIC-COMPATIBLE route profile and its\n// compatibility fences (#3010, under the DeepSeek V4 support program #3006).\n//\n// DeepSeek documents an Anthropic-compatible API at\n// https://api.deepseek.com/anthropic (source: https://api-docs.deepseek.com/guides/anthropic_api)\n// so a Claude Code-style client can front DeepSeek through the SAME `/anthropic`\n// base + `/v1/messages` composition fak already speaks to real Anthropic. This file\n// is the load-bearing wire that decides whether that fronting is SAFE — it does NOT\n// change the default Anthropic provider behavior for real Claude (that stays exactly\n// as internal/agent's anthropicAdapter composes it); it adds a DeepSeek profile on\n// top of the same composition and fences the documented compatibility GAPS so fak\n// fails loud instead of silently shipping a block the route drops.\n//\n// The three gaps this route MUST fence (all from the DeepSeek compatibility table,\n// https://api-docs.deepseek.com/guides/anthropic_api):\n// 1. Content blocks: images / documents / redacted-thinking / code-execution / MCP\n// blocks are NOT all supported. Sending one silently would lose data. The fence\n// refuses with a CLOSED reason (fail loud), never drops silently.\n// 2. `cache_control` is IGNORED by DeepSeek. So a cache_control breakpoint on this\n// route is a provider no-op, NOT a fak-managed prompt-cache saving — booking it\n// as one would misprice value accounting. IsFakManagedCacheSaving reports false.\n// 3. `thinking` is supported but `budget_tokens` is IGNORED. The request builder\n// enables thinking WITHOUT a budget so no caller is misled into thinking the\n// budget bound the response.\n//\n// PROVENANCE: this profile OBSERVES a third-party route. Everything it reports about\n// DeepSeek's behavior (ignored cache_control, model fallback) is PROVIDER-OBSERVED,\n// never fak-authored — the same OBSERVED-vs-WITNESSED discipline deepseek_pricing.go\n// applies to the billing counters.\n\n// DeepSeekAnthropicMessagesPath is the endpoint SUFFIX the Anthropic Messages wire\n// appends to a base URL. It MUST match internal/agent's anthropicAdapter.Endpoint\n// (`joinEndpoint(baseURL, \"/v1/messages\")`) so the DeepSeek route composes byte-for-byte\n// the way the real-Anthropic route does; deepseek_anthropic_test.go pins the parity.\nconst DeepSeekAnthropicMessagesPath = \"/v1/messages\"\n\n// DeepSeekAnthropicVersion is the anthropic-version header the DeepSeek route sends,\n// matching the value the real-Anthropic adapter uses. DeepSeek's Anthropic-compatible\n// endpoint accepts the same versioned Messages wire.\nconst DeepSeekAnthropicVersion = \"2023-06-01\"\n\n// closed-vocabulary reasons for a REFUSED unsupported content block. A refusal names\n// exactly one of these — never free text — so a caller (or a test) can branch on the\n// reason and an operator sees a stable, greppable token instead of prose.\nconst (\n\tDeepSeekAnthropicUnsupportedImage = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_IMAGE\"\n\tDeepSeekAnthropicUnsupportedDocument = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_DOCUMENT\"\n\tDeepSeekAnthropicUnsupportedRedactedThinking = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_REDACTED_THINKING\"\n\tDeepSeekAnthropicUnsupportedCodeExecution = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_CODE_EXECUTION\"\n\tDeepSeekAnthropicUnsupportedMCP = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_MCP\"\n)\n\n// deepSeekAnthropicUnsupportedBlocks maps each Anthropic-wire content-block `type`\n// DeepSeek does NOT support to its closed refusal reason. The keys are the documented\n// unsupported set (image, document, redacted thinking, code execution, MCP); a\n// server_tool_use block is DeepSeek's code-execution surface, and mcp_tool_use /\n// mcp_tool_result are the MCP content blocks. Everything NOT in this map is treated\n// as supported (text / tool_use / tool_result / thinking), so a genuinely new block\n// type fails OPEN as supported rather than being wrongly refused — the fence only\n// speaks about blocks the compatibility table names.\nvar deepSeekAnthropicUnsupportedBlocks = map[string]string{\n\t\"image\": DeepSeekAnthropicUnsupportedImage,\n\t\"document\": DeepSeekAnthropicUnsupportedDocument,\n\t\"redacted_thinking\": DeepSeekAnthropicUnsupportedRedactedThinking,\n\t\"code_execution\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\"server_tool_use\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\"mcp_tool_use\": DeepSeekAnthropicUnsupportedMCP,\n\t\"mcp_tool_result\": DeepSeekAnthropicUnsupportedMCP,\n}\n\n// DeepSeekAnthropicUnsupportedBlockReason reports whether a content-block `type` is\n// unsupported on the DeepSeek Anthropic route, and if so its closed refusal reason.\n// The type is normalized (trim + lower) so wire casing does not slip a block past the\n// fence.\nfunc DeepSeekAnthropicUnsupportedBlockReason(blockType string) (reason string, unsupported bool) {\n\tr, ok := deepSeekAnthropicUnsupportedBlocks[strings.ToLower(strings.TrimSpace(blockType))]\n\treturn r, ok\n}\n\n// DeepSeekAnthropicModelSource labels HOW a model id was resolved, so a ledger can tell\n// an operator-chosen direct id apart from a Claude-name mapping fak applied.\ntype DeepSeekAnthropicModelSource string\n\nconst (\n\t// DeepSeekAnthropicModelDirect: the caller already named a real DeepSeek V4 model\n\t// id (deepseek-v4-pro / deepseek-v4-flash). This is the PREFERRED form.\n\tDeepSeekAnthropicModelDirect DeepSeekAnthropicModelSource = \"direct\"\n\t// DeepSeekAnthropicModelClaudeMapped: the caller named a Claude family (opus /\n\t// sonnet / haiku) and fak applied its OWN explicit mapping. This is fak's policy,\n\t// NOT reliance on DeepSeek's server-side \"unknown model → Flash\" fallback.\n\tDeepSeekAnthropicModelClaudeMapped DeepSeekAnthropicModelSource = \"claude-name-mapped:deepseek-coding-agents-guide\"\n)\n\n// ResolveDeepSeekAnthropicModel turns the model id a Claude Code-style client sent into\n// the concrete DeepSeek V4 id fak will put on the wire. It PREFERS a direct\n// deepseek-v4-* id; for a Claude family name it applies fak's OWN explicit mapping\n// (Opus/Sonnet → Pro, Haiku → Flash) taken from DeepSeek's coding-agents guide\n// (https://api-docs.deepseek.com/guides/coding_agents), so the id on the wire is one\n// fak chose rather than leaving DeepSeek to silently coerce an unknown name to Flash.\n//\n// The DeepSeek Claude Code guide sets ANTHROPIC_MODEL=deepseek-v4-pro[1m]; the trailing\n// `[1m]` selects the 1M-context variant. It is stripped for the routing decision (both\n// variants map to the same base id) but reported via has1M so the caller can preserve\n// the marker on the wire if it wants the extended window.\n//\n// It FAILS LOUD (err != nil) on any other name rather than defaulting to Flash — the\n// issue is explicit that DeepSeek's unsupported-model fallback is NOT fak's routing\n// policy.\nfunc ResolveDeepSeekAnthropicModel(requested string) (model string, source DeepSeekAnthropicModelSource, has1M bool, err error) {\n\tnorm := strings.ToLower(strings.TrimSpace(requested))\n\tif strings.HasSuffix(norm, \"[1m]\") {\n\t\thas1M = true\n\t\tnorm = strings.TrimSpace(strings.TrimSuffix(norm, \"[1m]\"))\n\t}\n\tswitch norm {\n\tcase \"\":\n\t\treturn \"\", \"\", has1M, fmt.Errorf(\"deepseek-anthropic: empty model id; name deepseek-v4-pro or deepseek-v4-flash explicitly\")\n\tcase modelroute.DeepSeekV4ProModel:\n\t\treturn modelroute.DeepSeekV4ProModel, DeepSeekAnthropicModelDirect, has1M, nil\n\tcase modelroute.DeepSeekV4FlashModel:\n\t\treturn modelroute.DeepSeekV4FlashModel, DeepSeekAnthropicModelDirect, has1M, nil\n\t}\n\tswitch {\n\tcase strings.Contains(norm, \"opus\"), strings.Contains(norm, \"sonnet\"):\n\t\treturn modelroute.DeepSeekV4ProModel, DeepSeekAnthropicModelClaudeMapped, has1M, nil\n\tcase strings.Contains(norm, \"haiku\"):\n\t\treturn modelroute.DeepSeekV4FlashModel, DeepSeekAnthropicModelClaudeMapped, has1M, nil\n\t}\n\treturn \"\", \"\", has1M, fmt.Errorf(\"deepseek-anthropic: unroutable model %q; name deepseek-v4-pro/deepseek-v4-flash or a Claude opus/sonnet/haiku family (fak will not rely on DeepSeek's silent unknown-model→Flash fallback)\", requested)\n}\n\n// DeepSeek's Anthropic-compatible effort control (`output_config.effort`) accepts these\n// two levels for a coding agent; DeepSeek's guide recommends \"max\" for Claude Code.\nconst (\n\tDeepSeekAnthropicEffortHigh = \"high\"\n\tDeepSeekAnthropicEffortMax = \"max\"\n)\n\n// ValidateDeepSeekAnthropicEffort accepts an empty effort (control unset) or one of the\n// two documented levels, and refuses anything else so a typo'd effort is caught here\n// rather than silently ignored by the upstream.\nfunc ValidateDeepSeekAnthropicEffort(effort string) error {\n\tswitch strings.ToLower(strings.TrimSpace(effort)) {\n\tcase \"\", DeepSeekAnthropicEffortHigh, DeepSeekAnthropicEffortMax:\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"deepseek-anthropic: invalid output_config.effort %q; want %q or %q\", effort, DeepSeekAnthropicEffortHigh, DeepSeekAnthropicEffortMax)\n}\n\n// DeepSeekAnthropicCacheControlProvenance is the provenance label for a cache_control\n// breakpoint sent to the DeepSeek Anthropic route: DeepSeek IGNORES cache_control, so\n// it is a provider no-op, never a fak-authored saving.\nconst DeepSeekAnthropicCacheControlProvenance = \"provider-ignored\"\n\n// IsFakManagedCacheSaving reports whether a cache_control breakpoint on the DeepSeek\n// Anthropic route should be booked as a fak-managed prompt-cache SAVING. It is always\n// false: DeepSeek ignores cache_control, so any implied discount is provider-observed\n// economics (DeepSeek's own default context caching, priced by deepseek_pricing.go),\n// not something a fak mechanism shaped. This is the fence against reporting an ignored\n// cache_control as a fak cache effect.\nfunc IsFakManagedCacheSaving() bool { return false }\n\n// DeepSeekAnthropicProfile is a launch preset that fronts DeepSeek through the\n// Anthropic-compatible route. The zero value is not usable; use\n// NewDeepSeekAnthropicProfile so BaseURL defaults correctly.\ntype DeepSeekAnthropicProfile struct {\n\t// BaseURL is the Anthropic-compatible base; defaults to\n\t// modelroute.DeepSeekAnthropicBaseURL. The Messages path is appended by\n\t// MessagesURL, exactly as the real-Anthropic adapter composes it.\n\tBaseURL string\n\t// APIKey is the DeepSeek API key (the operator-owned DEEPSEEK_API_KEY secret). It\n\t// rides as x-api-key and is NEVER placed in the URL, model id, or body.\n\tAPIKey string\n\t// Model is the DeepSeek V4 id to put on the wire (already resolved via\n\t// ResolveDeepSeekAnthropicModel).\n\tModel string\n\t// Effort, if set, is emitted as output_config.effort (\"high\"/\"max\").\n\tEffort string\n}\n\n// NewDeepSeekAnthropicProfile builds a profile, defaulting BaseURL to DeepSeek's\n// documented Anthropic-compatible root and resolving the model id (failing loud on an\n// unroutable name). The [1m] extended-window marker, if the caller sent one, is\n// preserved on the wire model id.\nfunc NewDeepSeekAnthropicProfile(apiKey, requestedModel, effort string) (DeepSeekAnthropicProfile, error) {\n\tmodel, _, has1M, err := ResolveDeepSeekAnthropicModel(requestedModel)\n\tif err != nil {\n\t\treturn DeepSeekAnthropicProfile{}, err\n\t}\n\tif err := ValidateDeepSeekAnthropicEffort(effort); err != nil {\n\t\treturn DeepSeekAnthropicProfile{}, err\n\t}\n\twireModel := model\n\tif has1M {\n\t\twireModel += \"[1m]\"\n\t}\n\treturn DeepSeekAnthropicProfile{\n\t\tBaseURL: modelroute.DeepSeekAnthropicBaseURL,\n\t\tAPIKey: apiKey,\n\t\tModel: wireModel,\n\t\tEffort: effort,\n\t}, nil\n}\n\n// MessagesURL composes the concrete POST target the same way the real-Anthropic route\n// does: the base URL with a trailing slash trimmed, plus the Messages path. With the\n// default base that is https://api.deepseek.com/anthropic/v1/messages.\nfunc (p DeepSeekAnthropicProfile) MessagesURL() string {\n\tbase := p.BaseURL\n\tif base == \"\" {\n\t\tbase = modelroute.DeepSeekAnthropicBaseURL\n\t}\n\treturn strings.TrimRight(base, \"/\") + DeepSeekAnthropicMessagesPath\n}\n\n// headers builds the request headers. The API key rides ONLY as x-api-key (DeepSeek's\n// documented scheme for the Anthropic-compatible route); it never lands in the URL or\n// body, so a request/response log cannot leak it from those surfaces.\nfunc (p DeepSeekAnthropicProfile) headers() map[string]string {\n\th := map[string]string{\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"anthropic-version\": DeepSeekAnthropicVersion,\n\t}\n\tif p.APIKey != \"\" {\n\t\th[\"x-api-key\"] = p.APIKey\n\t}\n\treturn h\n}\n\n// DeepSeekAnthropicBlock is one Anthropic-wire content block on the REQUEST side. Only\n// the `type` is load-bearing for the fence; Text is carried for text blocks.\ntype DeepSeekAnthropicBlock struct {\n\tType string `json:\"type\"`\n\tText string `json:\"text,omitempty\"`\n}\n\n// DeepSeekAnthropicMessage is one role-tagged message with its content blocks.\ntype DeepSeekAnthropicMessage struct {\n\tRole string `json:\"role\"`\n\tContent []DeepSeekAnthropicBlock `json:\"content\"`\n}\n\n// FenceContentBlocks scans every request block and returns an error naming EVERY\n// unsupported block (deduped, sorted, each with its closed reason) so fak fails loud\n// rather than shipping an image/document/redacted-thinking/code-exec/MCP block to a\n// route that would silently drop it. Returns nil when every block is supported.\nfunc FenceContentBlocks(messages []DeepSeekAnthropicMessage) error {\n\tseen := map[string]bool{}\n\tvar refusals []string\n\tfor _, m := range messages {\n\t\tfor _, b := range m.Content {\n\t\t\tif reason, bad := DeepSeekAnthropicUnsupportedBlockReason(b.Type); bad {\n\t\t\t\tkey := strings.ToLower(strings.TrimSpace(b.Type)) + \" \" + reason\n\t\t\t\tif !seen[key] {\n\t\t\t\t\tseen[key] = true\n\t\t\t\t\trefusals = append(refusals, fmt.Sprintf(\"%s(%s)\", strings.ToLower(strings.TrimSpace(b.Type)), reason))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif len(refusals) == 0 {\n\t\treturn nil\n\t}\n\tsort.Strings(refusals)\n\treturn fmt.Errorf(\"deepseek-anthropic: unsupported content block(s) refused: %s\", strings.Join(refusals, \", \"))\n}\n\n// deepSeekAnthropicWireRequest is the JSON body posted to the Messages endpoint. It\n// enables thinking WITHOUT budget_tokens (DeepSeek ignores it) and emits\n// output_config.effort only when set.\ntype deepSeekAnthropicWireRequest struct {\n\tModel string `json:\"model\"`\n\tMaxTokens int `json:\"max_tokens\"`\n\tSystem string `json:\"system,omitempty\"`\n\tMessages []DeepSeekAnthropicMessage `json:\"messages\"`\n\tThinking *struct {\n\t\tType string `json:\"type\"`\n\t} `json:\"thinking,omitempty\"`\n\tOutputConfig *struct {\n\t\tEffort string `json:\"effort\"`\n\t} `json:\"output_config,omitempty\"`\n}\n\n// DeepSeekAnthropicResponseBlock is one block of the assistant response, covering the\n// text and thinking blocks DeepSeek returns (thinking is supported on this route).\ntype DeepSeekAnthropicResponseBlock struct {\n\tType string `json:\"type\"`\n\tText string `json:\"text,omitempty\"`\n\tThinking string `json:\"thinking,omitempty\"`\n\tSignature string `json:\"signature,omitempty\"`\n}\n\n// DeepSeekAnthropicResponse is the decoded Messages response. Usage carries DeepSeek's\n// own top-level cache hit/miss counters (the OBSERVED numbers deepseek_pricing.go\n// prices), NOT a fak-managed cache saving.\ntype DeepSeekAnthropicResponse struct {\n\tID string `json:\"id\"`\n\tModel string `json:\"model\"`\n\tRole string `json:\"role\"`\n\tContent []DeepSeekAnthropicResponseBlock `json:\"content\"`\n\tStopReason string `json:\"stop_reason\"`\n\tUsage struct {\n\t\tInputTokens int `json:\"input_tokens\"`\n\t\tOutputTokens int `json:\"output_tokens\"`\n\t\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\t\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\t} `json:\"usage\"`\n}\n\n// PostMessages builds the request (fencing unsupported blocks and validating effort),\n// posts it to MessagesURL with the x-api-key credential, and decodes the response. It\n// is the offline-testable seam: pass an *http.Client aimed at an httptest server to\n// exercise the full round trip without a network. thinking enables the thinking block;\n// budget_tokens is intentionally never sent (DeepSeek ignores it).\nfunc (p DeepSeekAnthropicProfile) PostMessages(ctx context.Context, client *http.Client, system string, messages []DeepSeekAnthropicMessage, maxTokens int, thinking bool) (*DeepSeekAnthropicResponse, error) {\n\tif strings.TrimSpace(p.Model) == \"\" {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: profile has no model; build it with NewDeepSeekAnthropicProfile\")\n\t}\n\tif err := ValidateDeepSeekAnthropicEffort(p.Effort); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := FenceContentBlocks(messages); err != nil {\n\t\treturn nil, err\n\t}\n\tbody := deepSeekAnthropicWireRequest{\n\t\tModel: p.Model,\n\t\tMaxTokens: maxTokens,\n\t\tSystem: system,\n\t\tMessages: messages,\n\t}\n\tif thinking {\n\t\tbody.Thinking = &struct {\n\t\t\tType string `json:\"type\"`\n\t\t}{Type: \"enabled\"}\n\t}\n\tif e := strings.TrimSpace(p.Effort); e != \"\" {\n\t\tbody.OutputConfig = &struct {\n\t\t\tEffort string `json:\"effort\"`\n\t\t}{Effort: e}\n\t}\n\traw, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: marshal request: %w\", err)\n\t}\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, p.MessagesURL(), bytes.NewReader(raw))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: build request: %w\", err)\n\t}\n\tfor k, v := range p.headers() {\n\t\treq.Header.Set(k, v)\n\t}\n\tif client == nil {\n\t\tclient = &http.Client{Timeout: durEnv(\"FAK_DEEPSEEK_ANTHROPIC_TIMEOUT_S\", 120*time.Second)}\n\t}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: post: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\tpayload, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: read response: %w\", err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: upstream status %d: %s\", resp.StatusCode, strings.TrimSpace(string(payload)))\n\t}\n\tvar out DeepSeekAnthropicResponse\n\tif err := json.Unmarshal(payload, &out); err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: decode response: %w\", err)\n\t}\n\treturn &out, nil\n}\n\n// ClaudeCodeDeepSeekEnv is the machine-readable source for the Claude Code runbook\n// (rendered by the human runbook, #3012): the environment a Claude Code-style client\n// sets to route through fak into DeepSeek. The API-key entry is the NAME of the\n// operator-owned secret env var (DEEPSEEK_API_KEY), never a literal secret, so the\n// preset can be printed or committed without leaking a credential. requestedModel is\n// resolved (and validated) so an unroutable name is caught before it reaches a runbook.\nfunc ClaudeCodeDeepSeekEnv(requestedModel string) (map[string]string, error) {\n\tprof, err := NewDeepSeekAnthropicProfile(\"\", requestedModel, \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn map[string]string{\n\t\t\"ANTHROPIC_BASE_URL\": modelroute.DeepSeekAnthropicBaseURL,\n\t\t\"ANTHROPIC_MODEL\": prof.Model,\n\t\t\"ANTHROPIC_API_KEY\": \"$\" + modelroute.DeepSeekAPIKeyEnv,\n\t\t\"ANTHROPIC_AUTH_KEY\": modelroute.DeepSeekAPIKeyEnv,\n\t}, nil\n}\n\n// ClaudeCodeDeepSeekRunbook renders the env preset as copy-pasteable shell lines for\n// one platform: \"unix\" (bash/zsh `export`) or \"windows\" (PowerShell `$env:`). Both\n// reference the operator-owned DEEPSEEK_API_KEY by name and never inline a secret.\nfunc ClaudeCodeDeepSeekRunbook(platform, requestedModel string) ([]string, error) {\n\tenv, err := ClaudeCodeDeepSeekEnv(requestedModel)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkeys := make([]string, 0, len(env))\n\tfor k := range env {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\tvar lines []string\n\tswitch strings.ToLower(strings.TrimSpace(platform)) {\n\tcase \"unix\", \"linux\", \"macos\", \"darwin\":\n\t\tfor _, k := range keys {\n\t\t\tlines = append(lines, fmt.Sprintf(\"export %s=%s\", k, env[k]))\n\t\t}\n\tcase \"windows\", \"powershell\":\n\t\tfor _, k := range keys {\n\t\t\t// PowerShell reads a shell var with $env:NAME; a $-prefixed value from the\n\t\t\t// preset (the secret reference) is rewritten to PowerShell's own form.\n\t\t\tval := env[k]\n\t\t\tif strings.HasPrefix(val, \"$\") {\n\t\t\t\tval = \"$env:\" + strings.TrimPrefix(val, \"$\")\n\t\t\t}\n\t\t\tlines = append(lines, fmt.Sprintf(\"$env:%s = \\\"%s\\\"\", k, val))\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: unknown runbook platform %q; want \\\"unix\\\" or \\\"windows\\\"\", platform)\n\t}\n\treturn lines, nil\n}\n"}, "cacdefdc11d9927fabb5d874c3d6fec435976c40": {"t": 1785749572.8056278, "n": 20477, "h": "29aa3675032bff44", "c": "package gateway\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strings\"\n\n\t\"github.com/anthony-chaudhary/fak/internal/modelroute\"\n)\n\n// deepseek_anthropic.go — the DeepSeek ANTHROPIC-COMPATIBLE route profile and its\n// compatibility fences (#3010, under the DeepSeek V4 support program #3006).\n//\n// DeepSeek documents an Anthropic-compatible API at\n// https://api.deepseek.com/anthropic (source: https://api-docs.deepseek.com/guides/anthropic_api)\n// so a Claude Code-style client can front DeepSeek through the SAME `/anthropic`\n// base + `/v1/messages` composition fak already speaks to real Anthropic. This file\n// is the load-bearing wire that decides whether that fronting is SAFE — it does NOT\n// change the default Anthropic provider behavior for real Claude (that stays exactly\n// as internal/agent's anthropicAdapter composes it); it adds a DeepSeek profile on\n// top of the same composition and fences the documented compatibility GAPS so fak\n// fails loud instead of silently shipping a block the route drops.\n//\n// The three gaps this route MUST fence (all from the DeepSeek compatibility table,\n// https://api-docs.deepseek.com/guides/anthropic_api):\n// 1. Content blocks: images / documents / redacted-thinking / code-execution / MCP\n// blocks are NOT all supported. Sending one silently would lose data. The fence\n// refuses with a CLOSED reason (fail loud), never drops silently.\n// 2. `cache_control` is IGNORED by DeepSeek. So a cache_control breakpoint on this\n// route is a provider no-op, NOT a fak-managed prompt-cache saving — booking it\n// as one would misprice value accounting. IsFakManagedCacheSaving reports false.\n// 3. `thinking` is supported but `budget_tokens` is IGNORED. The request builder\n// enables thinking WITHOUT a budget so no caller is misled into thinking the\n// budget bound the response.\n//\n// PROVENANCE: this profile OBSERVES a third-party route. Everything it reports about\n// DeepSeek's behavior (ignored cache_control, model fallback) is PROVIDER-OBSERVED,\n// never fak-authored — the same OBSERVED-vs-WITNESSED discipline deepseek_pricing.go\n// applies to the billing counters.\n\n// DeepSeekAnthropicMessagesPath is the endpoint SUFFIX the Anthropic Messages wire\n// appends to a base URL. It MUST match internal/agent's anthropicAdapter.Endpoint\n// (`joinEndpoint(baseURL, \"/v1/messages\")`) so the DeepSeek route composes byte-for-byte\n// the way the real-Anthropic route does; deepseek_anthropic_test.go pins the parity.\nconst DeepSeekAnthropicMessagesPath = \"/v1/messages\"\n\n// DeepSeekAnthropicVersion is the anthropic-version header the DeepSeek route sends,\n// matching the value the real-Anthropic adapter uses. DeepSeek's Anthropic-compatible\n// endpoint accepts the same versioned Messages wire.\nconst DeepSeekAnthropicVersion = \"2023-06-01\"\n\n// closed-vocabulary reasons for a REFUSED unsupported content block. A refusal names\n// exactly one of these — never free text — so a caller (or a test) can branch on the\n// reason and an operator sees a stable, greppable token instead of prose.\nconst (\n\tDeepSeekAnthropicUnsupportedImage = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_IMAGE\"\n\tDeepSeekAnthropicUnsupportedDocument = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_DOCUMENT\"\n\tDeepSeekAnthropicUnsupportedRedactedThinking = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_REDACTED_THINKING\"\n\tDeepSeekAnthropicUnsupportedCodeExecution = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_CODE_EXECUTION\"\n\tDeepSeekAnthropicUnsupportedMCP = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_MCP\"\n)\n\n// deepSeekAnthropicUnsupportedBlocks maps each Anthropic-wire content-block `type`\n// DeepSeek does NOT support to its closed refusal reason. The keys are the documented\n// unsupported set (image, document, redacted thinking, code execution, MCP); a\n// server_tool_use block is DeepSeek's code-execution surface, and mcp_tool_use /\n// mcp_tool_result are the MCP content blocks. Everything NOT in this map is treated\n// as supported (text / tool_use / tool_result / thinking), so a genuinely new block\n// type fails OPEN as supported rather than being wrongly refused — the fence only\n// speaks about blocks the compatibility table names.\nvar deepSeekAnthropicUnsupportedBlocks = map[string]string{\n\t\"image\": DeepSeekAnthropicUnsupportedImage,\n\t\"document\": DeepSeekAnthropicUnsupportedDocument,\n\t\"redacted_thinking\": DeepSeekAnthropicUnsupportedRedactedThinking,\n\t\"code_execution\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\"server_tool_use\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\"mcp_tool_use\": DeepSeekAnthropicUnsupportedMCP,\n\t\"mcp_tool_result\": DeepSeekAnthropicUnsupportedMCP,\n}\n\n// DeepSeekAnthropicUnsupportedBlockReason reports whether a content-block `type` is\n// unsupported on the DeepSeek Anthropic route, and if so its closed refusal reason.\n// The type is normalized (trim + lower) so wire casing does not slip a block past the\n// fence.\nfunc DeepSeekAnthropicUnsupportedBlockReason(blockType string) (reason string, unsupported bool) {\n\tr, ok := deepSeekAnthropicUnsupportedBlocks[strings.ToLower(strings.TrimSpace(blockType))]\n\treturn r, ok\n}\n\n// DeepSeekAnthropicModelSource labels HOW a model id was resolved, so a ledger can tell\n// an operator-chosen direct id apart from a Claude-name mapping fak applied.\ntype DeepSeekAnthropicModelSource string\n\nconst (\n\t// DeepSeekAnthropicModelDirect: the caller already named a real DeepSeek V4 model\n\t// id (deepseek-v4-pro / deepseek-v4-flash). This is the PREFERRED form.\n\tDeepSeekAnthropicModelDirect DeepSeekAnthropicModelSource = \"direct\"\n\t// DeepSeekAnthropicModelClaudeMapped: the caller named a Claude family (opus /\n\t// sonnet / haiku) and fak applied its OWN explicit mapping. This is fak's policy,\n\t// NOT reliance on DeepSeek's server-side \"unknown model → Flash\" fallback.\n\tDeepSeekAnthropicModelClaudeMapped DeepSeekAnthropicModelSource = \"claude-name-mapped:deepseek-coding-agents-guide\"\n)\n\n// ResolveDeepSeekAnthropicModel turns the model id a Claude Code-style client sent into\n// the concrete DeepSeek V4 id fak will put on the wire. It PREFERS a direct\n// deepseek-v4-* id; for a Claude family name it applies fak's OWN explicit mapping\n// (Opus/Sonnet → Pro, Haiku → Flash) taken from DeepSeek's coding-agents guide\n// (https://api-docs.deepseek.com/guides/coding_agents), so the id on the wire is one\n// fak chose rather than leaving DeepSeek to silently coerce an unknown name to Flash.\n//\n// The DeepSeek Claude Code guide sets ANTHROPIC_MODEL=deepseek-v4-pro[1m]; the trailing\n// `[1m]` selects the 1M-context variant. It is stripped for the routing decision (both\n// variants map to the same base id) but reported via has1M so the caller can preserve\n// the marker on the wire if it wants the extended window.\n//\n// It FAILS LOUD (err != nil) on any other name rather than defaulting to Flash — the\n// issue is explicit that DeepSeek's unsupported-model fallback is NOT fak's routing\n// policy.\nfunc ResolveDeepSeekAnthropicModel(requested string) (model string, source DeepSeekAnthropicModelSource, has1M bool, err error) {\n\tnorm := strings.ToLower(strings.TrimSpace(requested))\n\tif strings.HasSuffix(norm, \"[1m]\") {\n\t\thas1M = true\n\t\tnorm = strings.TrimSpace(strings.TrimSuffix(norm, \"[1m]\"))\n\t}\n\tswitch norm {\n\tcase \"\":\n\t\treturn \"\", \"\", has1M, fmt.Errorf(\"deepseek-anthropic: empty model id; name deepseek-v4-pro or deepseek-v4-flash explicitly\")\n\tcase modelroute.DeepSeekV4ProModel:\n\t\treturn modelroute.DeepSeekV4ProModel, DeepSeekAnthropicModelDirect, has1M, nil\n\tcase modelroute.DeepSeekV4FlashModel:\n\t\treturn modelroute.DeepSeekV4FlashModel, DeepSeekAnthropicModelDirect, has1M, nil\n\t}\n\tswitch {\n\tcase strings.Contains(norm, \"opus\"), strings.Contains(norm, \"sonnet\"):\n\t\treturn modelroute.DeepSeekV4ProModel, DeepSeekAnthropicModelClaudeMapped, has1M, nil\n\tcase strings.Contains(norm, \"haiku\"):\n\t\treturn modelroute.DeepSeekV4FlashModel, DeepSeekAnthropicModelClaudeMapped, has1M, nil\n\t}\n\treturn \"\", \"\", has1M, fmt.Errorf(\"deepseek-anthropic: unroutable model %q; name deepseek-v4-pro/deepseek-v4-flash or a Claude opus/sonnet/haiku family (fak will not rely on DeepSeek's silent unknown-model→Flash fallback)\", requested)\n}\n\n// DeepSeek's Anthropic-compatible effort control (`output_config.effort`) accepts these\n// two levels for a coding agent; DeepSeek's guide recommends \"max\" for Claude Code.\nconst (\n\tDeepSeekAnthropicEffortHigh = \"high\"\n\tDeepSeekAnthropicEffortMax = \"max\"\n)\n\n// ValidateDeepSeekAnthropicEffort accepts an empty effort (control unset) or one of the\n// two documented levels, and refuses anything else so a typo'd effort is caught here\n// rather than silently ignored by the upstream.\nfunc ValidateDeepSeekAnthropicEffort(effort string) error {\n\tswitch strings.ToLower(strings.TrimSpace(effort)) {\n\tcase \"\", DeepSeekAnthropicEffortHigh, DeepSeekAnthropicEffortMax:\n\t\treturn nil\n\t}\n\treturn fmt.Errorf(\"deepseek-anthropic: invalid output_config.effort %q; want %q or %q\", effort, DeepSeekAnthropicEffortHigh, DeepSeekAnthropicEffortMax)\n}\n\n// DeepSeekAnthropicCacheControlProvenance is the provenance label for a cache_control\n// breakpoint sent to the DeepSeek Anthropic route: DeepSeek IGNORES cache_control, so\n// it is a provider no-op, never a fak-authored saving.\nconst DeepSeekAnthropicCacheControlProvenance = \"provider-ignored\"\n\n// IsFakManagedCacheSaving reports whether a cache_control breakpoint on the DeepSeek\n// Anthropic route should be booked as a fak-managed prompt-cache SAVING. It is always\n// false: DeepSeek ignores cache_control, so any implied discount is provider-observed\n// economics (DeepSeek's own default context caching, priced by deepseek_pricing.go),\n// not something a fak mechanism shaped. This is the fence against reporting an ignored\n// cache_control as a fak cache effect.\nfunc IsFakManagedCacheSaving() bool { return false }\n\n// DeepSeekAnthropicProfile is a launch preset that fronts DeepSeek through the\n// Anthropic-compatible route. The zero value is not usable; use\n// NewDeepSeekAnthropicProfile so BaseURL defaults correctly.\ntype DeepSeekAnthropicProfile struct {\n\t// BaseURL is the Anthropic-compatible base; defaults to\n\t// modelroute.DeepSeekAnthropicBaseURL. The Messages path is appended by\n\t// MessagesURL, exactly as the real-Anthropic adapter composes it.\n\tBaseURL string\n\t// APIKey is the DeepSeek API key (the operator-owned DEEPSEEK_API_KEY secret). It\n\t// rides as x-api-key and is NEVER placed in the URL, model id, or body.\n\tAPIKey string\n\t// Model is the DeepSeek V4 id to put on the wire (already resolved via\n\t// ResolveDeepSeekAnthropicModel).\n\tModel string\n\t// Effort, if set, is emitted as output_config.effort (\"high\"/\"max\").\n\tEffort string\n}\n\n// NewDeepSeekAnthropicProfile builds a profile, defaulting BaseURL to DeepSeek's\n// documented Anthropic-compatible root and resolving the model id (failing loud on an\n// unroutable name). The [1m] extended-window marker, if the caller sent one, is\n// preserved on the wire model id.\nfunc NewDeepSeekAnthropicProfile(apiKey, requestedModel, effort string) (DeepSeekAnthropicProfile, error) {\n\tmodel, _, has1M, err := ResolveDeepSeekAnthropicModel(requestedModel)\n\tif err != nil {\n\t\treturn DeepSeekAnthropicProfile{}, err\n\t}\n\tif err := ValidateDeepSeekAnthropicEffort(effort); err != nil {\n\t\treturn DeepSeekAnthropicProfile{}, err\n\t}\n\twireModel := model\n\tif has1M {\n\t\twireModel += \"[1m]\"\n\t}\n\treturn DeepSeekAnthropicProfile{\n\t\tBaseURL: modelroute.DeepSeekAnthropicBaseURL,\n\t\tAPIKey: apiKey,\n\t\tModel: wireModel,\n\t\tEffort: effort,\n\t}, nil\n}\n\n// MessagesURL composes the concrete POST target the same way the real-Anthropic route\n// does: the base URL with a trailing slash trimmed, plus the Messages path. With the\n// default base that is https://api.deepseek.com/anthropic/v1/messages.\nfunc (p DeepSeekAnthropicProfile) MessagesURL() string {\n\tbase := p.BaseURL\n\tif base == \"\" {\n\t\tbase = modelroute.DeepSeekAnthropicBaseURL\n\t}\n\treturn strings.TrimRight(base, \"/\") + DeepSeekAnthropicMessagesPath\n}\n\n// headers builds the request headers. The API key rides ONLY as x-api-key (DeepSeek's\n// documented scheme for the Anthropic-compatible route); it never lands in the URL or\n// body, so a request/response log cannot leak it from those surfaces.\nfunc (p DeepSeekAnthropicProfile) headers() map[string]string {\n\th := map[string]string{\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"anthropic-version\": DeepSeekAnthropicVersion,\n\t}\n\tif p.APIKey != \"\" {\n\t\th[\"x-api-key\"] = p.APIKey\n\t}\n\treturn h\n}\n\n// DeepSeekAnthropicBlock is one Anthropic-wire content block on the REQUEST side. Only\n// the `type` is load-bearing for the fence; Text is carried for text blocks.\ntype DeepSeekAnthropicBlock struct {\n\tType string `json:\"type\"`\n\tText string `json:\"text,omitempty\"`\n}\n\n// DeepSeekAnthropicMessage is one role-tagged message with its content blocks.\ntype DeepSeekAnthropicMessage struct {\n\tRole string `json:\"role\"`\n\tContent []DeepSeekAnthropicBlock `json:\"content\"`\n}\n\n// FenceContentBlocks scans every request block and returns an error naming EVERY\n// unsupported block (deduped, sorted, each with its closed reason) so fak fails loud\n// rather than shipping an image/document/redacted-thinking/code-exec/MCP block to a\n// route that would silently drop it. Returns nil when every block is supported.\nfunc FenceContentBlocks(messages []DeepSeekAnthropicMessage) error {\n\tseen := map[string]bool{}\n\tvar refusals []string\n\tfor _, m := range messages {\n\t\tfor _, b := range m.Content {\n\t\t\tif reason, bad := DeepSeekAnthropicUnsupportedBlockReason(b.Type); bad {\n\t\t\t\tkey := strings.ToLower(strings.TrimSpace(b.Type)) + \" \" + reason\n\t\t\t\tif !seen[key] {\n\t\t\t\t\tseen[key] = true\n\t\t\t\t\trefusals = append(refusals, fmt.Sprintf(\"%s(%s)\", strings.ToLower(strings.TrimSpace(b.Type)), reason))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif len(refusals) == 0 {\n\t\treturn nil\n\t}\n\tsort.Strings(refusals)\n\treturn fmt.Errorf(\"deepseek-anthropic: unsupported content block(s) refused: %s\", strings.Join(refusals, \", \"))\n}\n\n// deepSeekAnthropicWireRequest is the JSON body posted to the Messages endpoint. It\n// enables thinking WITHOUT budget_tokens (DeepSeek ignores it) and emits\n// output_config.effort only when set.\ntype deepSeekAnthropicWireRequest struct {\n\tModel string `json:\"model\"`\n\tMaxTokens int `json:\"max_tokens\"`\n\tSystem string `json:\"system,omitempty\"`\n\tMessages []DeepSeekAnthropicMessage `json:\"messages\"`\n\tThinking *struct {\n\t\tType string `json:\"type\"`\n\t} `json:\"thinking,omitempty\"`\n\tOutputConfig *struct {\n\t\tEffort string `json:\"effort\"`\n\t} `json:\"output_config,omitempty\"`\n}\n\n// DeepSeekAnthropicResponseBlock is one block of the assistant response, covering the\n// text and thinking blocks DeepSeek returns (thinking is supported on this route).\ntype DeepSeekAnthropicResponseBlock struct {\n\tType string `json:\"type\"`\n\tText string `json:\"text,omitempty\"`\n\tThinking string `json:\"thinking,omitempty\"`\n\tSignature string `json:\"signature,omitempty\"`\n}\n\n// DeepSeekAnthropicResponse is the decoded Messages response. Usage carries DeepSeek's\n// own top-level cache hit/miss counters (the OBSERVED numbers deepseek_pricing.go\n// prices), NOT a fak-managed cache saving.\ntype DeepSeekAnthropicResponse struct {\n\tID string `json:\"id\"`\n\tModel string `json:\"model\"`\n\tRole string `json:\"role\"`\n\tContent []DeepSeekAnthropicResponseBlock `json:\"content\"`\n\tStopReason string `json:\"stop_reason\"`\n\tUsage struct {\n\t\tInputTokens int `json:\"input_tokens\"`\n\t\tOutputTokens int `json:\"output_tokens\"`\n\t\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\t\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\t} `json:\"usage\"`\n}\n\n// PostMessages builds the request (fencing unsupported blocks and validating effort),\n// posts it to MessagesURL with the x-api-key credential, and decodes the response. It\n// is the offline-testable seam: pass an *http.Client aimed at an httptest server to\n// exercise the full round trip without a network. thinking enables the thinking block;\n// budget_tokens is intentionally never sent (DeepSeek ignores it).\nfunc (p DeepSeekAnthropicProfile) PostMessages(ctx context.Context, client *http.Client, system string, messages []DeepSeekAnthropicMessage, maxTokens int, thinking bool) (*DeepSeekAnthropicResponse, error) {\n\tif strings.TrimSpace(p.Model) == \"\" {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: profile has no model; build it with NewDeepSeekAnthropicProfile\")\n\t}\n\tif err := ValidateDeepSeekAnthropicEffort(p.Effort); err != nil {\n\t\treturn nil, err\n\t}\n\tif err := FenceContentBlocks(messages); err != nil {\n\t\treturn nil, err\n\t}\n\tbody := deepSeekAnthropicWireRequest{\n\t\tModel: p.Model,\n\t\tMaxTokens: maxTokens,\n\t\tSystem: system,\n\t\tMessages: messages,\n\t}\n\tif thinking {\n\t\tbody.Thinking = &struct {\n\t\t\tType string `json:\"type\"`\n\t\t}{Type: \"enabled\"}\n\t}\n\tif e := strings.TrimSpace(p.Effort); e != \"\" {\n\t\tbody.OutputConfig = &struct {\n\t\t\tEffort string `json:\"effort\"`\n\t\t}{Effort: e}\n\t}\n\traw, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: marshal request: %w\", err)\n\t}\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, p.MessagesURL(), bytes.NewReader(raw))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: build request: %w\", err)\n\t}\n\tfor k, v := range p.headers() {\n\t\treq.Header.Set(k, v)\n\t}\n\tif client == nil {\n\t\tclient = http.DefaultClient\n\t}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: post: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\tpayload, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: read response: %w\", err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: upstream status %d: %s\", resp.StatusCode, strings.TrimSpace(string(payload)))\n\t}\n\tvar out DeepSeekAnthropicResponse\n\tif err := json.Unmarshal(payload, &out); err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: decode response: %w\", err)\n\t}\n\treturn &out, nil\n}\n\n// ClaudeCodeDeepSeekEnv is the machine-readable source for the Claude Code runbook\n// (rendered by the human runbook, #3012): the environment a Claude Code-style client\n// sets to route through fak into DeepSeek. The API-key entry is the NAME of the\n// operator-owned secret env var (DEEPSEEK_API_KEY), never a literal secret, so the\n// preset can be printed or committed without leaking a credential. requestedModel is\n// resolved (and validated) so an unroutable name is caught before it reaches a runbook.\nfunc ClaudeCodeDeepSeekEnv(requestedModel string) (map[string]string, error) {\n\tprof, err := NewDeepSeekAnthropicProfile(\"\", requestedModel, \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn map[string]string{\n\t\t\"ANTHROPIC_BASE_URL\": modelroute.DeepSeekAnthropicBaseURL,\n\t\t\"ANTHROPIC_MODEL\": prof.Model,\n\t\t\"ANTHROPIC_API_KEY\": \"$\" + modelroute.DeepSeekAPIKeyEnv,\n\t\t\"ANTHROPIC_AUTH_KEY\": modelroute.DeepSeekAPIKeyEnv,\n\t}, nil\n}\n\n// ClaudeCodeDeepSeekRunbook renders the env preset as copy-pasteable shell lines for\n// one platform: \"unix\" (bash/zsh `export`) or \"windows\" (PowerShell `$env:`). Both\n// reference the operator-owned DEEPSEEK_API_KEY by name and never inline a secret.\nfunc ClaudeCodeDeepSeekRunbook(platform, requestedModel string) ([]string, error) {\n\tenv, err := ClaudeCodeDeepSeekEnv(requestedModel)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkeys := make([]string, 0, len(env))\n\tfor k := range env {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\tvar lines []string\n\tswitch strings.ToLower(strings.TrimSpace(platform)) {\n\tcase \"unix\", \"linux\", \"macos\", \"darwin\":\n\t\tfor _, k := range keys {\n\t\t\tlines = append(lines, fmt.Sprintf(\"export %s=%s\", k, env[k]))\n\t\t}\n\tcase \"windows\", \"powershell\":\n\t\tfor _, k := range keys {\n\t\t\t// PowerShell reads a shell var with $env:NAME; a $-prefixed value from the\n\t\t\t// preset (the secret reference) is rewritten to PowerShell's own form.\n\t\t\tval := env[k]\n\t\t\tif strings.HasPrefix(val, \"$\") {\n\t\t\t\tval = \"$env:\" + strings.TrimPrefix(val, \"$\")\n\t\t\t}\n\t\t\tlines = append(lines, fmt.Sprintf(\"$env:%s = \\\"%s\\\"\", k, val))\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: unknown runbook platform %q; want \\\"unix\\\" or \\\"windows\\\"\", platform)\n\t}\n\treturn lines, nil\n}\n"}, "13f5f1a6e0391eb28bcbea759890fb009251e5d1": {"t": 1786067419.7461286, "n": 22553, "h": "cde3b9b7dffdb8d0", "c": "package gateway\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/anthony-chaudhary/fak/internal/modelroute\"\n)\n\n// deepseek_anthropic.go — the DeepSeek ANTHROPIC-COMPATIBLE route profile and its\n// compatibility fences (#3010, under the DeepSeek V4 support program #3006).\n//\n// DeepSeek documents an Anthropic-compatible API at\n// https://api.deepseek.com/anthropic (source: https://api-docs.deepseek.com/guides/anthropic_api)\n// so a Claude Code-style client can front DeepSeek through the SAME `/anthropic`\n// base + `/v1/messages` composition fak already speaks to real Anthropic. This file\n// is the load-bearing wire that decides whether that fronting is SAFE — it does NOT\n// change the default Anthropic provider behavior for real Claude (that stays exactly\n// as internal/agent's anthropicAdapter composes it); it adds a DeepSeek profile on\n// top of the same composition and fences the documented compatibility GAPS so fak\n// fails loud instead of silently shipping a block the route drops.\n//\n// The three gaps this route MUST fence (all from the DeepSeek compatibility table,\n// https://api-docs.deepseek.com/guides/anthropic_api):\n// 1. Content blocks: images / documents / redacted-thinking / code-execution / MCP\n// blocks are NOT all supported. Sending one silently would lose data. The fence\n// refuses with a CLOSED reason (fail loud), never drops silently.\n// 2. `cache_control` is IGNORED by DeepSeek. So a cache_control breakpoint on this\n// route is a provider no-op, NOT a fak-managed prompt-cache saving — booking it\n// as one would misprice value accounting. IsFakManagedCacheSaving reports false.\n// 3. `thinking` is supported but `budget_tokens` is IGNORED. The request builder\n// enables thinking WITHOUT a budget so no caller is misled into thinking the\n// budget bound the response.\n//\n// PROVENANCE: this profile OBSERVES a third-party route. Everything it reports about\n// DeepSeek's behavior (ignored cache_control, model fallback) is PROVIDER-OBSERVED,\n// never fak-authored — the same OBSERVED-vs-WITNESSED discipline deepseek_pricing.go\n// applies to the billing counters.\n\n// DeepSeekAnthropicMessagesPath is the endpoint SUFFIX the Anthropic Messages wire\n// appends to a base URL. It MUST match internal/agent's anthropicAdapter.Endpoint\n// (`joinEndpoint(baseURL, \"/v1/messages\")`) so the DeepSeek route composes byte-for-byte\n// the way the real-Anthropic route does; deepseek_anthropic_test.go pins the parity.\nconst DeepSeekAnthropicMessagesPath = \"/v1/messages\"\n\n// DeepSeekAnthropicVersion is the anthropic-version header the DeepSeek route sends,\n// matching the value the real-Anthropic adapter uses. DeepSeek's Anthropic-compatible\n// endpoint accepts the same versioned Messages wire.\nconst DeepSeekAnthropicVersion = \"2023-06-01\"\n\n// closed-vocabulary reasons for a REFUSED unsupported content block. A refusal names\n// exactly one of these — never free text — so a caller (or a test) can branch on the\n// reason and an operator sees a stable, greppable token instead of prose.\nconst (\n\tDeepSeekAnthropicUnsupportedImage = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_IMAGE\"\n\tDeepSeekAnthropicUnsupportedDocument = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_DOCUMENT\"\n\tDeepSeekAnthropicUnsupportedRedactedThinking = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_REDACTED_THINKING\"\n\tDeepSeekAnthropicUnsupportedCodeExecution = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_CODE_EXECUTION\"\n\tDeepSeekAnthropicUnsupportedMCP = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_MCP\"\n)\n\n// deepSeekAnthropicUnsupportedBlocks maps each Anthropic-wire content-block `type`\n// DeepSeek does NOT support to its closed refusal reason. The keys are the documented\n// unsupported set (image, document, redacted thinking, code execution, MCP); a\n// server_tool_use block is DeepSeek's code-execution surface, and mcp_tool_use /\n// mcp_tool_result are the MCP content blocks. Everything NOT in this map is treated\n// as supported (text / tool_use / tool_result / thinking), so a genuinely new block\n// type fails OPEN as supported rather than being wrongly refused — the fence only\n// speaks about blocks the compatibility table names.\nvar deepSeekAnthropicUnsupportedBlocks = map[string]string{\n\t\"image\": DeepSeekAnthropicUnsupportedImage,\n\t\"document\": DeepSeekAnthropicUnsupportedDocument,\n\t\"redacted_thinking\": DeepSeekAnthropicUnsupportedRedactedThinking,\n\t\"code_execution\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\"server_tool_use\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\"mcp_tool_use\": DeepSeekAnthropicUnsupportedMCP,\n\t\"mcp_tool_result\": DeepSeekAnthropicUnsupportedMCP,\n}\n\n// DeepSeekAnthropicUnsupportedBlockReason reports whether a content-block `type` is\n// unsupported on the DeepSeek Anthropic route, and if so its closed refusal reason.\n// The type is normalized (trim + lower) so wire casing does not slip a block past the\n// fence.\nfunc DeepSeekAnthropicUnsupportedBlockReason(blockType string) (reason string, unsupported bool) {\n\tr, ok := deepSeekAnthropicUnsupportedBlocks[strings.ToLower(strings.TrimSpace(blockType))]\n\treturn r, ok\n}\n\n// DeepSeekAnthropicModelSource labels HOW a model id was resolved, so a ledger can tell\n// an operator-chosen direct id apart from a Claude-name mapping fak applied.\ntype DeepSeekAnthropicModelSource string\n\nconst (\n\t// DeepSeekAnthropicModelDirect: the caller already named a real DeepSeek V4 model\n\t// id (deepseek-v4-pro / deepseek-v4-flash). This is the PREFERRED form.\n\tDeepSeekAnthropicModelDirect DeepSeekAnthropicModelSource = \"direct\"\n\t// DeepSeekAnthropicModelClaudeMapped: the caller named a Claude family (opus /\n\t// sonnet / haiku) and fak applied its OWN explicit mapping. This is fak's policy,\n\t// NOT reliance on DeepSeek's server-side \"unknown model → Flash\" fallback.\n\tDeepSeekAnthropicModelClaudeMapped DeepSeekAnthropicModelSource = \"claude-name-mapped:deepseek-coding-agents-guide\"\n)\n\n// ResolveDeepSeekAnthropicModel turns the model id a Claude Code-style client sent into\n// the concrete DeepSeek V4 id fak will put on the wire. It PREFERS a direct\n// deepseek-v4-* id; for a Claude family name it applies fak's OWN explicit mapping\n// (Opus/Sonnet → Pro, Haiku → Flash) taken from DeepSeek's coding-agents guide\n// (https://api-docs.deepseek.com/guides/coding_agents), so the id on the wire is one\n// fak chose rather than leaving DeepSeek to silently coerce an unknown name to Flash.\n//\n// The DeepSeek Claude Code guide sets ANTHROPIC_MODEL=deepseek-v4-pro[1m]; the trailing\n// `[1m]` selects the 1M-context variant. It is stripped for the routing decision (both\n// variants map to the same base id) but reported via has1M so the caller can preserve\n// the marker on the wire if it wants the extended window.\n//\n// It FAILS LOUD (err != nil) on any other name rather than defaulting to Flash — the\n// issue is explicit that DeepSeek's unsupported-model fallback is NOT fak's routing\n// policy.\nfunc ResolveDeepSeekAnthropicModel(requested string) (model string, source DeepSeekAnthropicModelSource, has1M bool, err error) {\n\tnorm := strings.ToLower(strings.TrimSpace(requested))\n\tif strings.HasSuffix(norm, \"[1m]\") {\n\t\thas1M = true\n\t\tnorm = strings.TrimSpace(strings.TrimSuffix(norm, \"[1m]\"))\n\t}\n\tswitch norm {\n\tcase \"\":\n\t\treturn \"\", \"\", has1M, fmt.Errorf(\"deepseek-anthropic: empty model id; name deepseek-v4-pro or deepseek-v4-flash explicitly\")\n\tcase modelroute.DeepSeekV4ProModel:\n\t\treturn modelroute.DeepSeekV4ProModel, DeepSeekAnthropicModelDirect, has1M, nil\n\tcase modelroute.DeepSeekV4FlashModel:\n\t\treturn modelroute.DeepSeekV4FlashModel, DeepSeekAnthropicModelDirect, has1M, nil\n\t}\n\tswitch {\n\tcase strings.Contains(norm, \"opus\"), strings.Contains(norm, \"sonnet\"):\n\t\treturn modelroute.DeepSeekV4ProModel, DeepSeekAnthropicModelClaudeMapped, has1M, nil\n\tcase strings.Contains(norm, \"haiku\"):\n\t\treturn modelroute.DeepSeekV4FlashModel, DeepSeekAnthropicModelClaudeMapped, has1M, nil\n\t}\n\treturn \"\", \"\", has1M, fmt.Errorf(\"deepseek-anthropic: unroutable model %q; name deepseek-v4-pro/deepseek-v4-flash or a Claude opus/sonnet/haiku family (fak will not rely on DeepSeek's silent unknown-model→Flash fallback)\", requested)\n}\n\n// DeepSeek's Anthropic-compatible effort control (`output_config.effort`) accepts these\n// two levels for a coding agent; DeepSeek's guide recommends \"max\" for Claude Code.\nconst (\n\tDeepSeekAnthropicEffortHigh = \"high\"\n\tDeepSeekAnthropicEffortMax = \"max\"\n)\n\n// deepSeekAnthropicWireEfforts is the effort vocabulary this route can put on the wire —\n// the clamp target set for ResolveDeepSeekAnthropicEffort.\nvar deepSeekAnthropicWireEfforts = []string{DeepSeekAnthropicEffortHigh, DeepSeekAnthropicEffortMax}\n\n// ResolveDeepSeekAnthropicEffort maps a CANONICAL fak effort tier onto the two levels this\n// route speaks, SATURATING to the nearest supported tier rather than erroring a request the\n// provider can serve one rung away (#4069). fak's canonical ladder carries rungs DeepSeek\n// simply does not name (none/minimal/low/medium/xhigh), and refusing them made a caller's\n// perfectly ordinary `--effort low` fail the whole request; it now runs at high, and xhigh\n// runs at max (the tie breaks upward — see modelroute.SaturateEffort).\n//\n// The TYPO FENCE this replaced is preserved exactly where it earns its keep. Saturation is\n// only ever applied to a tier fak can PLACE on its ladder; a token off the ladder entirely\n// (\"extreem\") is still refused, because it carries no ordering to clamp with and is far\n// likelier a mistake than an intent. So the route degrades gracefully across a known\n// vocabulary gap while still failing loud on a genuinely unknown one.\n//\n// Returns (\"\", nil) for a blank effort: the control stays unset, which is not an error.\nfunc ResolveDeepSeekAnthropicEffort(effort string) (string, error) {\n\tif strings.TrimSpace(effort) == \"\" {\n\t\treturn \"\", nil\n\t}\n\tif tier := modelroute.SaturateEffort(effort, deepSeekAnthropicWireEfforts); tier != \"\" {\n\t\treturn tier, nil\n\t}\n\treturn \"\", fmt.Errorf(\"deepseek-anthropic: unknown output_config.effort %q; want a canonical effort tier (none/minimal/low/medium/high/xhigh/max), which is saturated to %q or %q\", effort, DeepSeekAnthropicEffortHigh, DeepSeekAnthropicEffortMax)\n}\n\n// ValidateDeepSeekAnthropicEffort reports whether an effort can reach the wire at all: nil\n// for a blank control and for any canonical tier (which ResolveDeepSeekAnthropicEffort\n// saturates into range), an error only for a tier off fak's ladder. Callers that need the\n// tier actually sent should use ResolveDeepSeekAnthropicEffort instead.\nfunc ValidateDeepSeekAnthropicEffort(effort string) error {\n\t_, err := ResolveDeepSeekAnthropicEffort(effort)\n\treturn err\n}\n\n// DeepSeekAnthropicCacheControlProvenance is the provenance label for a cache_control\n// breakpoint sent to the DeepSeek Anthropic route: DeepSeek IGNORES cache_control, so\n// it is a provider no-op, never a fak-authored saving.\nconst DeepSeekAnthropicCacheControlProvenance = \"provider-ignored\"\n\n// IsFakManagedCacheSaving reports whether a cache_control breakpoint on the DeepSeek\n// Anthropic route should be booked as a fak-managed prompt-cache SAVING. It is always\n// false: DeepSeek ignores cache_control, so any implied discount is provider-observed\n// economics (DeepSeek's own default context caching, priced by deepseek_pricing.go),\n// not something a fak mechanism shaped. This is the fence against reporting an ignored\n// cache_control as a fak cache effect.\nfunc IsFakManagedCacheSaving() bool { return false }\n\n// DeepSeekAnthropicProfile is a launch preset that fronts DeepSeek through the\n// Anthropic-compatible route. The zero value is not usable; use\n// NewDeepSeekAnthropicProfile so BaseURL defaults correctly.\ntype DeepSeekAnthropicProfile struct {\n\t// BaseURL is the Anthropic-compatible base; defaults to\n\t// modelroute.DeepSeekAnthropicBaseURL. The Messages path is appended by\n\t// MessagesURL, exactly as the real-Anthropic adapter composes it.\n\tBaseURL string\n\t// APIKey is the DeepSeek API key (the operator-owned DEEPSEEK_API_KEY secret). It\n\t// rides as x-api-key and is NEVER placed in the URL, model id, or body.\n\tAPIKey string\n\t// Model is the DeepSeek V4 id to put on the wire (already resolved via\n\t// ResolveDeepSeekAnthropicModel).\n\tModel string\n\t// Effort, if set, is emitted as output_config.effort. It holds a tier DeepSeek names\n\t// (\"high\"/\"max\"); NewDeepSeekAnthropicProfile has already saturated any other\n\t// canonical tier onto that set (#4069).\n\tEffort string\n}\n\n// NewDeepSeekAnthropicProfile builds a profile, defaulting BaseURL to DeepSeek's\n// documented Anthropic-compatible root and resolving the model id (failing loud on an\n// unroutable name). The [1m] extended-window marker, if the caller sent one, is\n// preserved on the wire model id.\nfunc NewDeepSeekAnthropicProfile(apiKey, requestedModel, effort string) (DeepSeekAnthropicProfile, error) {\n\tmodel, _, has1M, err := ResolveDeepSeekAnthropicModel(requestedModel)\n\tif err != nil {\n\t\treturn DeepSeekAnthropicProfile{}, err\n\t}\n\twireEffort, err := ResolveDeepSeekAnthropicEffort(effort)\n\tif err != nil {\n\t\treturn DeepSeekAnthropicProfile{}, err\n\t}\n\twireModel := model\n\tif has1M {\n\t\twireModel += \"[1m]\"\n\t}\n\treturn DeepSeekAnthropicProfile{\n\t\tBaseURL: modelroute.DeepSeekAnthropicBaseURL,\n\t\tAPIKey: apiKey,\n\t\tModel: wireModel,\n\t\tEffort: wireEffort,\n\t}, nil\n}\n\n// MessagesURL composes the concrete POST target the same way the real-Anthropic route\n// does: the base URL with a trailing slash trimmed, plus the Messages path. With the\n// default base that is https://api.deepseek.com/anthropic/v1/messages.\nfunc (p DeepSeekAnthropicProfile) MessagesURL() string {\n\tbase := p.BaseURL\n\tif base == \"\" {\n\t\tbase = modelroute.DeepSeekAnthropicBaseURL\n\t}\n\treturn strings.TrimRight(base, \"/\") + DeepSeekAnthropicMessagesPath\n}\n\n// headers builds the request headers. The API key rides ONLY as x-api-key (DeepSeek's\n// documented scheme for the Anthropic-compatible route); it never lands in the URL or\n// body, so a request/response log cannot leak it from those surfaces.\nfunc (p DeepSeekAnthropicProfile) headers() map[string]string {\n\th := map[string]string{\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"anthropic-version\": DeepSeekAnthropicVersion,\n\t}\n\tif p.APIKey != \"\" {\n\t\th[\"x-api-key\"] = p.APIKey\n\t}\n\treturn h\n}\n\n// DeepSeekAnthropicBlock is one Anthropic-wire content block on the REQUEST side. Only\n// the `type` is load-bearing for the fence; Text is carried for text blocks.\ntype DeepSeekAnthropicBlock struct {\n\tType string `json:\"type\"`\n\tText string `json:\"text,omitempty\"`\n}\n\n// DeepSeekAnthropicMessage is one role-tagged message with its content blocks.\ntype DeepSeekAnthropicMessage struct {\n\tRole string `json:\"role\"`\n\tContent []DeepSeekAnthropicBlock `json:\"content\"`\n}\n\n// FenceContentBlocks scans every request block and returns an error naming EVERY\n// unsupported block (deduped, sorted, each with its closed reason) so fak fails loud\n// rather than shipping an image/document/redacted-thinking/code-exec/MCP block to a\n// route that would silently drop it. Returns nil when every block is supported.\nfunc FenceContentBlocks(messages []DeepSeekAnthropicMessage) error {\n\tseen := map[string]bool{}\n\tvar refusals []string\n\tfor _, m := range messages {\n\t\tfor _, b := range m.Content {\n\t\t\tif reason, bad := DeepSeekAnthropicUnsupportedBlockReason(b.Type); bad {\n\t\t\t\tkey := strings.ToLower(strings.TrimSpace(b.Type)) + \" \" + reason\n\t\t\t\tif !seen[key] {\n\t\t\t\t\tseen[key] = true\n\t\t\t\t\trefusals = append(refusals, fmt.Sprintf(\"%s(%s)\", strings.ToLower(strings.TrimSpace(b.Type)), reason))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif len(refusals) == 0 {\n\t\treturn nil\n\t}\n\tsort.Strings(refusals)\n\treturn fmt.Errorf(\"deepseek-anthropic: unsupported content block(s) refused: %s\", strings.Join(refusals, \", \"))\n}\n\n// deepSeekAnthropicWireRequest is the JSON body posted to the Messages endpoint. It\n// enables thinking WITHOUT budget_tokens (DeepSeek ignores it) and emits\n// output_config.effort only when set.\ntype deepSeekAnthropicWireRequest struct {\n\tModel string `json:\"model\"`\n\tMaxTokens int `json:\"max_tokens\"`\n\tSystem string `json:\"system,omitempty\"`\n\tMessages []DeepSeekAnthropicMessage `json:\"messages\"`\n\tThinking *struct {\n\t\tType string `json:\"type\"`\n\t} `json:\"thinking,omitempty\"`\n\tOutputConfig *struct {\n\t\tEffort string `json:\"effort\"`\n\t} `json:\"output_config,omitempty\"`\n}\n\n// DeepSeekAnthropicResponseBlock is one block of the assistant response, covering the\n// text and thinking blocks DeepSeek returns (thinking is supported on this route).\ntype DeepSeekAnthropicResponseBlock struct {\n\tType string `json:\"type\"`\n\tText string `json:\"text,omitempty\"`\n\tThinking string `json:\"thinking,omitempty\"`\n\tSignature string `json:\"signature,omitempty\"`\n}\n\n// DeepSeekAnthropicResponse is the decoded Messages response. Usage carries DeepSeek's\n// own top-level cache hit/miss counters (the OBSERVED numbers deepseek_pricing.go\n// prices), NOT a fak-managed cache saving.\ntype DeepSeekAnthropicResponse struct {\n\tID string `json:\"id\"`\n\tModel string `json:\"model\"`\n\tRole string `json:\"role\"`\n\tContent []DeepSeekAnthropicResponseBlock `json:\"content\"`\n\tStopReason string `json:\"stop_reason\"`\n\tUsage struct {\n\t\tInputTokens int `json:\"input_tokens\"`\n\t\tOutputTokens int `json:\"output_tokens\"`\n\t\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\t\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\t} `json:\"usage\"`\n}\n\n// PostMessages builds the request (fencing unsupported blocks and validating effort),\n// posts it to MessagesURL with the x-api-key credential, and decodes the response. It\n// is the offline-testable seam: pass an *http.Client aimed at an httptest server to\n// exercise the full round trip without a network. thinking enables the thinking block;\n// budget_tokens is intentionally never sent (DeepSeek ignores it).\nfunc (p DeepSeekAnthropicProfile) PostMessages(ctx context.Context, client *http.Client, system string, messages []DeepSeekAnthropicMessage, maxTokens int, thinking bool) (*DeepSeekAnthropicResponse, error) {\n\tif strings.TrimSpace(p.Model) == \"\" {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: profile has no model; build it with NewDeepSeekAnthropicProfile\")\n\t}\n\t// Saturate here too, not just in the constructor: a profile can be hand-built as a\n\t// struct literal, and the tier that reaches the wire must be one DeepSeek names.\n\twireEffort, err := ResolveDeepSeekAnthropicEffort(p.Effort)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := FenceContentBlocks(messages); err != nil {\n\t\treturn nil, err\n\t}\n\tbody := deepSeekAnthropicWireRequest{\n\t\tModel: p.Model,\n\t\tMaxTokens: maxTokens,\n\t\tSystem: system,\n\t\tMessages: messages,\n\t}\n\tif thinking {\n\t\tbody.Thinking = &struct {\n\t\t\tType string `json:\"type\"`\n\t\t}{Type: \"enabled\"}\n\t}\n\tif wireEffort != \"\" {\n\t\tbody.OutputConfig = &struct {\n\t\t\tEffort string `json:\"effort\"`\n\t\t}{Effort: wireEffort}\n\t}\n\traw, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: marshal request: %w\", err)\n\t}\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, p.MessagesURL(), bytes.NewReader(raw))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: build request: %w\", err)\n\t}\n\tfor k, v := range p.headers() {\n\t\treq.Header.Set(k, v)\n\t}\n\tif client == nil {\n\t\tclient = &http.Client{Timeout: durEnv(\"FAK_DEEPSEEK_ANTHROPIC_TIMEOUT_S\", 120*time.Second)}\n\t}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: post: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\tpayload, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: read response: %w\", err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: upstream status %d: %s\", resp.StatusCode, strings.TrimSpace(string(payload)))\n\t}\n\tvar out DeepSeekAnthropicResponse\n\tif err := json.Unmarshal(payload, &out); err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: decode response: %w\", err)\n\t}\n\treturn &out, nil\n}\n\n// ClaudeCodeDeepSeekEnv is the machine-readable source for the Claude Code runbook\n// (rendered by the human runbook, #3012): the environment a Claude Code-style client\n// sets to route through fak into DeepSeek. The API-key entry is the NAME of the\n// operator-owned secret env var (DEEPSEEK_API_KEY), never a literal secret, so the\n// preset can be printed or committed without leaking a credential. requestedModel is\n// resolved (and validated) so an unroutable name is caught before it reaches a runbook.\nfunc ClaudeCodeDeepSeekEnv(requestedModel string) (map[string]string, error) {\n\tprof, err := NewDeepSeekAnthropicProfile(\"\", requestedModel, \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn map[string]string{\n\t\t\"ANTHROPIC_BASE_URL\": modelroute.DeepSeekAnthropicBaseURL,\n\t\t\"ANTHROPIC_MODEL\": prof.Model,\n\t\t\"ANTHROPIC_API_KEY\": \"$\" + modelroute.DeepSeekAPIKeyEnv,\n\t\t\"ANTHROPIC_AUTH_KEY\": modelroute.DeepSeekAPIKeyEnv,\n\t}, nil\n}\n\n// ClaudeCodeDeepSeekRunbook renders the env preset as copy-pasteable shell lines for\n// one platform: \"unix\" (bash/zsh `export`) or \"windows\" (PowerShell `$env:`). Both\n// reference the operator-owned DEEPSEEK_API_KEY by name and never inline a secret.\nfunc ClaudeCodeDeepSeekRunbook(platform, requestedModel string) ([]string, error) {\n\tenv, err := ClaudeCodeDeepSeekEnv(requestedModel)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkeys := make([]string, 0, len(env))\n\tfor k := range env {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\tvar lines []string\n\tswitch strings.ToLower(strings.TrimSpace(platform)) {\n\tcase \"unix\", \"linux\", \"macos\", \"darwin\":\n\t\tfor _, k := range keys {\n\t\t\tlines = append(lines, fmt.Sprintf(\"export %s=%s\", k, env[k]))\n\t\t}\n\tcase \"windows\", \"powershell\":\n\t\tfor _, k := range keys {\n\t\t\t// PowerShell reads a shell var with $env:NAME; a $-prefixed value from the\n\t\t\t// preset (the secret reference) is rewritten to PowerShell's own form.\n\t\t\tval := env[k]\n\t\t\tif strings.HasPrefix(val, \"$\") {\n\t\t\t\tval = \"$env:\" + strings.TrimPrefix(val, \"$\")\n\t\t\t}\n\t\t\tlines = append(lines, fmt.Sprintf(\"$env:%s = \\\"%s\\\"\", k, val))\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: unknown runbook platform %q; want \\\"unix\\\" or \\\"windows\\\"\", platform)\n\t}\n\treturn lines, nil\n}\n"}, "c99f9d32ca81593f379f2688d676f9071cefe2df": {"t": 1786068285.737468, "n": 22553, "h": "cde3b9b7dffdb8d0", "c": "package gateway\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/anthony-chaudhary/fak/internal/modelroute\"\n)\n\n// deepseek_anthropic.go — the DeepSeek ANTHROPIC-COMPATIBLE route profile and its\n// compatibility fences (#3010, under the DeepSeek V4 support program #3006).\n//\n// DeepSeek documents an Anthropic-compatible API at\n// https://api.deepseek.com/anthropic (source: https://api-docs.deepseek.com/guides/anthropic_api)\n// so a Claude Code-style client can front DeepSeek through the SAME `/anthropic`\n// base + `/v1/messages` composition fak already speaks to real Anthropic. This file\n// is the load-bearing wire that decides whether that fronting is SAFE — it does NOT\n// change the default Anthropic provider behavior for real Claude (that stays exactly\n// as internal/agent's anthropicAdapter composes it); it adds a DeepSeek profile on\n// top of the same composition and fences the documented compatibility GAPS so fak\n// fails loud instead of silently shipping a block the route drops.\n//\n// The three gaps this route MUST fence (all from the DeepSeek compatibility table,\n// https://api-docs.deepseek.com/guides/anthropic_api):\n// 1. Content blocks: images / documents / redacted-thinking / code-execution / MCP\n// blocks are NOT all supported. Sending one silently would lose data. The fence\n// refuses with a CLOSED reason (fail loud), never drops silently.\n// 2. `cache_control` is IGNORED by DeepSeek. So a cache_control breakpoint on this\n// route is a provider no-op, NOT a fak-managed prompt-cache saving — booking it\n// as one would misprice value accounting. IsFakManagedCacheSaving reports false.\n// 3. `thinking` is supported but `budget_tokens` is IGNORED. The request builder\n// enables thinking WITHOUT a budget so no caller is misled into thinking the\n// budget bound the response.\n//\n// PROVENANCE: this profile OBSERVES a third-party route. Everything it reports about\n// DeepSeek's behavior (ignored cache_control, model fallback) is PROVIDER-OBSERVED,\n// never fak-authored — the same OBSERVED-vs-WITNESSED discipline deepseek_pricing.go\n// applies to the billing counters.\n\n// DeepSeekAnthropicMessagesPath is the endpoint SUFFIX the Anthropic Messages wire\n// appends to a base URL. It MUST match internal/agent's anthropicAdapter.Endpoint\n// (`joinEndpoint(baseURL, \"/v1/messages\")`) so the DeepSeek route composes byte-for-byte\n// the way the real-Anthropic route does; deepseek_anthropic_test.go pins the parity.\nconst DeepSeekAnthropicMessagesPath = \"/v1/messages\"\n\n// DeepSeekAnthropicVersion is the anthropic-version header the DeepSeek route sends,\n// matching the value the real-Anthropic adapter uses. DeepSeek's Anthropic-compatible\n// endpoint accepts the same versioned Messages wire.\nconst DeepSeekAnthropicVersion = \"2023-06-01\"\n\n// closed-vocabulary reasons for a REFUSED unsupported content block. A refusal names\n// exactly one of these — never free text — so a caller (or a test) can branch on the\n// reason and an operator sees a stable, greppable token instead of prose.\nconst (\n\tDeepSeekAnthropicUnsupportedImage = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_IMAGE\"\n\tDeepSeekAnthropicUnsupportedDocument = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_DOCUMENT\"\n\tDeepSeekAnthropicUnsupportedRedactedThinking = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_REDACTED_THINKING\"\n\tDeepSeekAnthropicUnsupportedCodeExecution = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_CODE_EXECUTION\"\n\tDeepSeekAnthropicUnsupportedMCP = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_MCP\"\n)\n\n// deepSeekAnthropicUnsupportedBlocks maps each Anthropic-wire content-block `type`\n// DeepSeek does NOT support to its closed refusal reason. The keys are the documented\n// unsupported set (image, document, redacted thinking, code execution, MCP); a\n// server_tool_use block is DeepSeek's code-execution surface, and mcp_tool_use /\n// mcp_tool_result are the MCP content blocks. Everything NOT in this map is treated\n// as supported (text / tool_use / tool_result / thinking), so a genuinely new block\n// type fails OPEN as supported rather than being wrongly refused — the fence only\n// speaks about blocks the compatibility table names.\nvar deepSeekAnthropicUnsupportedBlocks = map[string]string{\n\t\"image\": DeepSeekAnthropicUnsupportedImage,\n\t\"document\": DeepSeekAnthropicUnsupportedDocument,\n\t\"redacted_thinking\": DeepSeekAnthropicUnsupportedRedactedThinking,\n\t\"code_execution\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\"server_tool_use\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\"mcp_tool_use\": DeepSeekAnthropicUnsupportedMCP,\n\t\"mcp_tool_result\": DeepSeekAnthropicUnsupportedMCP,\n}\n\n// DeepSeekAnthropicUnsupportedBlockReason reports whether a content-block `type` is\n// unsupported on the DeepSeek Anthropic route, and if so its closed refusal reason.\n// The type is normalized (trim + lower) so wire casing does not slip a block past the\n// fence.\nfunc DeepSeekAnthropicUnsupportedBlockReason(blockType string) (reason string, unsupported bool) {\n\tr, ok := deepSeekAnthropicUnsupportedBlocks[strings.ToLower(strings.TrimSpace(blockType))]\n\treturn r, ok\n}\n\n// DeepSeekAnthropicModelSource labels HOW a model id was resolved, so a ledger can tell\n// an operator-chosen direct id apart from a Claude-name mapping fak applied.\ntype DeepSeekAnthropicModelSource string\n\nconst (\n\t// DeepSeekAnthropicModelDirect: the caller already named a real DeepSeek V4 model\n\t// id (deepseek-v4-pro / deepseek-v4-flash). This is the PREFERRED form.\n\tDeepSeekAnthropicModelDirect DeepSeekAnthropicModelSource = \"direct\"\n\t// DeepSeekAnthropicModelClaudeMapped: the caller named a Claude family (opus /\n\t// sonnet / haiku) and fak applied its OWN explicit mapping. This is fak's policy,\n\t// NOT reliance on DeepSeek's server-side \"unknown model → Flash\" fallback.\n\tDeepSeekAnthropicModelClaudeMapped DeepSeekAnthropicModelSource = \"claude-name-mapped:deepseek-coding-agents-guide\"\n)\n\n// ResolveDeepSeekAnthropicModel turns the model id a Claude Code-style client sent into\n// the concrete DeepSeek V4 id fak will put on the wire. It PREFERS a direct\n// deepseek-v4-* id; for a Claude family name it applies fak's OWN explicit mapping\n// (Opus/Sonnet → Pro, Haiku → Flash) taken from DeepSeek's coding-agents guide\n// (https://api-docs.deepseek.com/guides/coding_agents), so the id on the wire is one\n// fak chose rather than leaving DeepSeek to silently coerce an unknown name to Flash.\n//\n// The DeepSeek Claude Code guide sets ANTHROPIC_MODEL=deepseek-v4-pro[1m]; the trailing\n// `[1m]` selects the 1M-context variant. It is stripped for the routing decision (both\n// variants map to the same base id) but reported via has1M so the caller can preserve\n// the marker on the wire if it wants the extended window.\n//\n// It FAILS LOUD (err != nil) on any other name rather than defaulting to Flash — the\n// issue is explicit that DeepSeek's unsupported-model fallback is NOT fak's routing\n// policy.\nfunc ResolveDeepSeekAnthropicModel(requested string) (model string, source DeepSeekAnthropicModelSource, has1M bool, err error) {\n\tnorm := strings.ToLower(strings.TrimSpace(requested))\n\tif strings.HasSuffix(norm, \"[1m]\") {\n\t\thas1M = true\n\t\tnorm = strings.TrimSpace(strings.TrimSuffix(norm, \"[1m]\"))\n\t}\n\tswitch norm {\n\tcase \"\":\n\t\treturn \"\", \"\", has1M, fmt.Errorf(\"deepseek-anthropic: empty model id; name deepseek-v4-pro or deepseek-v4-flash explicitly\")\n\tcase modelroute.DeepSeekV4ProModel:\n\t\treturn modelroute.DeepSeekV4ProModel, DeepSeekAnthropicModelDirect, has1M, nil\n\tcase modelroute.DeepSeekV4FlashModel:\n\t\treturn modelroute.DeepSeekV4FlashModel, DeepSeekAnthropicModelDirect, has1M, nil\n\t}\n\tswitch {\n\tcase strings.Contains(norm, \"opus\"), strings.Contains(norm, \"sonnet\"):\n\t\treturn modelroute.DeepSeekV4ProModel, DeepSeekAnthropicModelClaudeMapped, has1M, nil\n\tcase strings.Contains(norm, \"haiku\"):\n\t\treturn modelroute.DeepSeekV4FlashModel, DeepSeekAnthropicModelClaudeMapped, has1M, nil\n\t}\n\treturn \"\", \"\", has1M, fmt.Errorf(\"deepseek-anthropic: unroutable model %q; name deepseek-v4-pro/deepseek-v4-flash or a Claude opus/sonnet/haiku family (fak will not rely on DeepSeek's silent unknown-model→Flash fallback)\", requested)\n}\n\n// DeepSeek's Anthropic-compatible effort control (`output_config.effort`) accepts these\n// two levels for a coding agent; DeepSeek's guide recommends \"max\" for Claude Code.\nconst (\n\tDeepSeekAnthropicEffortHigh = \"high\"\n\tDeepSeekAnthropicEffortMax = \"max\"\n)\n\n// deepSeekAnthropicWireEfforts is the effort vocabulary this route can put on the wire —\n// the clamp target set for ResolveDeepSeekAnthropicEffort.\nvar deepSeekAnthropicWireEfforts = []string{DeepSeekAnthropicEffortHigh, DeepSeekAnthropicEffortMax}\n\n// ResolveDeepSeekAnthropicEffort maps a CANONICAL fak effort tier onto the two levels this\n// route speaks, SATURATING to the nearest supported tier rather than erroring a request the\n// provider can serve one rung away (#4069). fak's canonical ladder carries rungs DeepSeek\n// simply does not name (none/minimal/low/medium/xhigh), and refusing them made a caller's\n// perfectly ordinary `--effort low` fail the whole request; it now runs at high, and xhigh\n// runs at max (the tie breaks upward — see modelroute.SaturateEffort).\n//\n// The TYPO FENCE this replaced is preserved exactly where it earns its keep. Saturation is\n// only ever applied to a tier fak can PLACE on its ladder; a token off the ladder entirely\n// (\"extreem\") is still refused, because it carries no ordering to clamp with and is far\n// likelier a mistake than an intent. So the route degrades gracefully across a known\n// vocabulary gap while still failing loud on a genuinely unknown one.\n//\n// Returns (\"\", nil) for a blank effort: the control stays unset, which is not an error.\nfunc ResolveDeepSeekAnthropicEffort(effort string) (string, error) {\n\tif strings.TrimSpace(effort) == \"\" {\n\t\treturn \"\", nil\n\t}\n\tif tier := modelroute.SaturateEffort(effort, deepSeekAnthropicWireEfforts); tier != \"\" {\n\t\treturn tier, nil\n\t}\n\treturn \"\", fmt.Errorf(\"deepseek-anthropic: unknown output_config.effort %q; want a canonical effort tier (none/minimal/low/medium/high/xhigh/max), which is saturated to %q or %q\", effort, DeepSeekAnthropicEffortHigh, DeepSeekAnthropicEffortMax)\n}\n\n// ValidateDeepSeekAnthropicEffort reports whether an effort can reach the wire at all: nil\n// for a blank control and for any canonical tier (which ResolveDeepSeekAnthropicEffort\n// saturates into range), an error only for a tier off fak's ladder. Callers that need the\n// tier actually sent should use ResolveDeepSeekAnthropicEffort instead.\nfunc ValidateDeepSeekAnthropicEffort(effort string) error {\n\t_, err := ResolveDeepSeekAnthropicEffort(effort)\n\treturn err\n}\n\n// DeepSeekAnthropicCacheControlProvenance is the provenance label for a cache_control\n// breakpoint sent to the DeepSeek Anthropic route: DeepSeek IGNORES cache_control, so\n// it is a provider no-op, never a fak-authored saving.\nconst DeepSeekAnthropicCacheControlProvenance = \"provider-ignored\"\n\n// IsFakManagedCacheSaving reports whether a cache_control breakpoint on the DeepSeek\n// Anthropic route should be booked as a fak-managed prompt-cache SAVING. It is always\n// false: DeepSeek ignores cache_control, so any implied discount is provider-observed\n// economics (DeepSeek's own default context caching, priced by deepseek_pricing.go),\n// not something a fak mechanism shaped. This is the fence against reporting an ignored\n// cache_control as a fak cache effect.\nfunc IsFakManagedCacheSaving() bool { return false }\n\n// DeepSeekAnthropicProfile is a launch preset that fronts DeepSeek through the\n// Anthropic-compatible route. The zero value is not usable; use\n// NewDeepSeekAnthropicProfile so BaseURL defaults correctly.\ntype DeepSeekAnthropicProfile struct {\n\t// BaseURL is the Anthropic-compatible base; defaults to\n\t// modelroute.DeepSeekAnthropicBaseURL. The Messages path is appended by\n\t// MessagesURL, exactly as the real-Anthropic adapter composes it.\n\tBaseURL string\n\t// APIKey is the DeepSeek API key (the operator-owned DEEPSEEK_API_KEY secret). It\n\t// rides as x-api-key and is NEVER placed in the URL, model id, or body.\n\tAPIKey string\n\t// Model is the DeepSeek V4 id to put on the wire (already resolved via\n\t// ResolveDeepSeekAnthropicModel).\n\tModel string\n\t// Effort, if set, is emitted as output_config.effort. It holds a tier DeepSeek names\n\t// (\"high\"/\"max\"); NewDeepSeekAnthropicProfile has already saturated any other\n\t// canonical tier onto that set (#4069).\n\tEffort string\n}\n\n// NewDeepSeekAnthropicProfile builds a profile, defaulting BaseURL to DeepSeek's\n// documented Anthropic-compatible root and resolving the model id (failing loud on an\n// unroutable name). The [1m] extended-window marker, if the caller sent one, is\n// preserved on the wire model id.\nfunc NewDeepSeekAnthropicProfile(apiKey, requestedModel, effort string) (DeepSeekAnthropicProfile, error) {\n\tmodel, _, has1M, err := ResolveDeepSeekAnthropicModel(requestedModel)\n\tif err != nil {\n\t\treturn DeepSeekAnthropicProfile{}, err\n\t}\n\twireEffort, err := ResolveDeepSeekAnthropicEffort(effort)\n\tif err != nil {\n\t\treturn DeepSeekAnthropicProfile{}, err\n\t}\n\twireModel := model\n\tif has1M {\n\t\twireModel += \"[1m]\"\n\t}\n\treturn DeepSeekAnthropicProfile{\n\t\tBaseURL: modelroute.DeepSeekAnthropicBaseURL,\n\t\tAPIKey: apiKey,\n\t\tModel: wireModel,\n\t\tEffort: wireEffort,\n\t}, nil\n}\n\n// MessagesURL composes the concrete POST target the same way the real-Anthropic route\n// does: the base URL with a trailing slash trimmed, plus the Messages path. With the\n// default base that is https://api.deepseek.com/anthropic/v1/messages.\nfunc (p DeepSeekAnthropicProfile) MessagesURL() string {\n\tbase := p.BaseURL\n\tif base == \"\" {\n\t\tbase = modelroute.DeepSeekAnthropicBaseURL\n\t}\n\treturn strings.TrimRight(base, \"/\") + DeepSeekAnthropicMessagesPath\n}\n\n// headers builds the request headers. The API key rides ONLY as x-api-key (DeepSeek's\n// documented scheme for the Anthropic-compatible route); it never lands in the URL or\n// body, so a request/response log cannot leak it from those surfaces.\nfunc (p DeepSeekAnthropicProfile) headers() map[string]string {\n\th := map[string]string{\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"anthropic-version\": DeepSeekAnthropicVersion,\n\t}\n\tif p.APIKey != \"\" {\n\t\th[\"x-api-key\"] = p.APIKey\n\t}\n\treturn h\n}\n\n// DeepSeekAnthropicBlock is one Anthropic-wire content block on the REQUEST side. Only\n// the `type` is load-bearing for the fence; Text is carried for text blocks.\ntype DeepSeekAnthropicBlock struct {\n\tType string `json:\"type\"`\n\tText string `json:\"text,omitempty\"`\n}\n\n// DeepSeekAnthropicMessage is one role-tagged message with its content blocks.\ntype DeepSeekAnthropicMessage struct {\n\tRole string `json:\"role\"`\n\tContent []DeepSeekAnthropicBlock `json:\"content\"`\n}\n\n// FenceContentBlocks scans every request block and returns an error naming EVERY\n// unsupported block (deduped, sorted, each with its closed reason) so fak fails loud\n// rather than shipping an image/document/redacted-thinking/code-exec/MCP block to a\n// route that would silently drop it. Returns nil when every block is supported.\nfunc FenceContentBlocks(messages []DeepSeekAnthropicMessage) error {\n\tseen := map[string]bool{}\n\tvar refusals []string\n\tfor _, m := range messages {\n\t\tfor _, b := range m.Content {\n\t\t\tif reason, bad := DeepSeekAnthropicUnsupportedBlockReason(b.Type); bad {\n\t\t\t\tkey := strings.ToLower(strings.TrimSpace(b.Type)) + \" \" + reason\n\t\t\t\tif !seen[key] {\n\t\t\t\t\tseen[key] = true\n\t\t\t\t\trefusals = append(refusals, fmt.Sprintf(\"%s(%s)\", strings.ToLower(strings.TrimSpace(b.Type)), reason))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif len(refusals) == 0 {\n\t\treturn nil\n\t}\n\tsort.Strings(refusals)\n\treturn fmt.Errorf(\"deepseek-anthropic: unsupported content block(s) refused: %s\", strings.Join(refusals, \", \"))\n}\n\n// deepSeekAnthropicWireRequest is the JSON body posted to the Messages endpoint. It\n// enables thinking WITHOUT budget_tokens (DeepSeek ignores it) and emits\n// output_config.effort only when set.\ntype deepSeekAnthropicWireRequest struct {\n\tModel string `json:\"model\"`\n\tMaxTokens int `json:\"max_tokens\"`\n\tSystem string `json:\"system,omitempty\"`\n\tMessages []DeepSeekAnthropicMessage `json:\"messages\"`\n\tThinking *struct {\n\t\tType string `json:\"type\"`\n\t} `json:\"thinking,omitempty\"`\n\tOutputConfig *struct {\n\t\tEffort string `json:\"effort\"`\n\t} `json:\"output_config,omitempty\"`\n}\n\n// DeepSeekAnthropicResponseBlock is one block of the assistant response, covering the\n// text and thinking blocks DeepSeek returns (thinking is supported on this route).\ntype DeepSeekAnthropicResponseBlock struct {\n\tType string `json:\"type\"`\n\tText string `json:\"text,omitempty\"`\n\tThinking string `json:\"thinking,omitempty\"`\n\tSignature string `json:\"signature,omitempty\"`\n}\n\n// DeepSeekAnthropicResponse is the decoded Messages response. Usage carries DeepSeek's\n// own top-level cache hit/miss counters (the OBSERVED numbers deepseek_pricing.go\n// prices), NOT a fak-managed cache saving.\ntype DeepSeekAnthropicResponse struct {\n\tID string `json:\"id\"`\n\tModel string `json:\"model\"`\n\tRole string `json:\"role\"`\n\tContent []DeepSeekAnthropicResponseBlock `json:\"content\"`\n\tStopReason string `json:\"stop_reason\"`\n\tUsage struct {\n\t\tInputTokens int `json:\"input_tokens\"`\n\t\tOutputTokens int `json:\"output_tokens\"`\n\t\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\t\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\t} `json:\"usage\"`\n}\n\n// PostMessages builds the request (fencing unsupported blocks and validating effort),\n// posts it to MessagesURL with the x-api-key credential, and decodes the response. It\n// is the offline-testable seam: pass an *http.Client aimed at an httptest server to\n// exercise the full round trip without a network. thinking enables the thinking block;\n// budget_tokens is intentionally never sent (DeepSeek ignores it).\nfunc (p DeepSeekAnthropicProfile) PostMessages(ctx context.Context, client *http.Client, system string, messages []DeepSeekAnthropicMessage, maxTokens int, thinking bool) (*DeepSeekAnthropicResponse, error) {\n\tif strings.TrimSpace(p.Model) == \"\" {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: profile has no model; build it with NewDeepSeekAnthropicProfile\")\n\t}\n\t// Saturate here too, not just in the constructor: a profile can be hand-built as a\n\t// struct literal, and the tier that reaches the wire must be one DeepSeek names.\n\twireEffort, err := ResolveDeepSeekAnthropicEffort(p.Effort)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := FenceContentBlocks(messages); err != nil {\n\t\treturn nil, err\n\t}\n\tbody := deepSeekAnthropicWireRequest{\n\t\tModel: p.Model,\n\t\tMaxTokens: maxTokens,\n\t\tSystem: system,\n\t\tMessages: messages,\n\t}\n\tif thinking {\n\t\tbody.Thinking = &struct {\n\t\t\tType string `json:\"type\"`\n\t\t}{Type: \"enabled\"}\n\t}\n\tif wireEffort != \"\" {\n\t\tbody.OutputConfig = &struct {\n\t\t\tEffort string `json:\"effort\"`\n\t\t}{Effort: wireEffort}\n\t}\n\traw, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: marshal request: %w\", err)\n\t}\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, p.MessagesURL(), bytes.NewReader(raw))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: build request: %w\", err)\n\t}\n\tfor k, v := range p.headers() {\n\t\treq.Header.Set(k, v)\n\t}\n\tif client == nil {\n\t\tclient = &http.Client{Timeout: durEnv(\"FAK_DEEPSEEK_ANTHROPIC_TIMEOUT_S\", 120*time.Second)}\n\t}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: post: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\tpayload, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: read response: %w\", err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: upstream status %d: %s\", resp.StatusCode, strings.TrimSpace(string(payload)))\n\t}\n\tvar out DeepSeekAnthropicResponse\n\tif err := json.Unmarshal(payload, &out); err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: decode response: %w\", err)\n\t}\n\treturn &out, nil\n}\n\n// ClaudeCodeDeepSeekEnv is the machine-readable source for the Claude Code runbook\n// (rendered by the human runbook, #3012): the environment a Claude Code-style client\n// sets to route through fak into DeepSeek. The API-key entry is the NAME of the\n// operator-owned secret env var (DEEPSEEK_API_KEY), never a literal secret, so the\n// preset can be printed or committed without leaking a credential. requestedModel is\n// resolved (and validated) so an unroutable name is caught before it reaches a runbook.\nfunc ClaudeCodeDeepSeekEnv(requestedModel string) (map[string]string, error) {\n\tprof, err := NewDeepSeekAnthropicProfile(\"\", requestedModel, \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn map[string]string{\n\t\t\"ANTHROPIC_BASE_URL\": modelroute.DeepSeekAnthropicBaseURL,\n\t\t\"ANTHROPIC_MODEL\": prof.Model,\n\t\t\"ANTHROPIC_API_KEY\": \"$\" + modelroute.DeepSeekAPIKeyEnv,\n\t\t\"ANTHROPIC_AUTH_KEY\": modelroute.DeepSeekAPIKeyEnv,\n\t}, nil\n}\n\n// ClaudeCodeDeepSeekRunbook renders the env preset as copy-pasteable shell lines for\n// one platform: \"unix\" (bash/zsh `export`) or \"windows\" (PowerShell `$env:`). Both\n// reference the operator-owned DEEPSEEK_API_KEY by name and never inline a secret.\nfunc ClaudeCodeDeepSeekRunbook(platform, requestedModel string) ([]string, error) {\n\tenv, err := ClaudeCodeDeepSeekEnv(requestedModel)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkeys := make([]string, 0, len(env))\n\tfor k := range env {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\tvar lines []string\n\tswitch strings.ToLower(strings.TrimSpace(platform)) {\n\tcase \"unix\", \"linux\", \"macos\", \"darwin\":\n\t\tfor _, k := range keys {\n\t\t\tlines = append(lines, fmt.Sprintf(\"export %s=%s\", k, env[k]))\n\t\t}\n\tcase \"windows\", \"powershell\":\n\t\tfor _, k := range keys {\n\t\t\t// PowerShell reads a shell var with $env:NAME; a $-prefixed value from the\n\t\t\t// preset (the secret reference) is rewritten to PowerShell's own form.\n\t\t\tval := env[k]\n\t\t\tif strings.HasPrefix(val, \"$\") {\n\t\t\t\tval = \"$env:\" + strings.TrimPrefix(val, \"$\")\n\t\t\t}\n\t\t\tlines = append(lines, fmt.Sprintf(\"$env:%s = \\\"%s\\\"\", k, val))\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: unknown runbook platform %q; want \\\"unix\\\" or \\\"windows\\\"\", platform)\n\t}\n\treturn lines, nil\n}\n"}, "cdad29dc07129694e98dccb7a97ca1403429dd9c": {"t": 1786068370.744907, "n": 22553, "h": "cde3b9b7dffdb8d0", "c": "package gateway\n\nimport (\n\t\"bytes\"\n\t\"context\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"sort\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/anthony-chaudhary/fak/internal/modelroute\"\n)\n\n// deepseek_anthropic.go — the DeepSeek ANTHROPIC-COMPATIBLE route profile and its\n// compatibility fences (#3010, under the DeepSeek V4 support program #3006).\n//\n// DeepSeek documents an Anthropic-compatible API at\n// https://api.deepseek.com/anthropic (source: https://api-docs.deepseek.com/guides/anthropic_api)\n// so a Claude Code-style client can front DeepSeek through the SAME `/anthropic`\n// base + `/v1/messages` composition fak already speaks to real Anthropic. This file\n// is the load-bearing wire that decides whether that fronting is SAFE — it does NOT\n// change the default Anthropic provider behavior for real Claude (that stays exactly\n// as internal/agent's anthropicAdapter composes it); it adds a DeepSeek profile on\n// top of the same composition and fences the documented compatibility GAPS so fak\n// fails loud instead of silently shipping a block the route drops.\n//\n// The three gaps this route MUST fence (all from the DeepSeek compatibility table,\n// https://api-docs.deepseek.com/guides/anthropic_api):\n// 1. Content blocks: images / documents / redacted-thinking / code-execution / MCP\n// blocks are NOT all supported. Sending one silently would lose data. The fence\n// refuses with a CLOSED reason (fail loud), never drops silently.\n// 2. `cache_control` is IGNORED by DeepSeek. So a cache_control breakpoint on this\n// route is a provider no-op, NOT a fak-managed prompt-cache saving — booking it\n// as one would misprice value accounting. IsFakManagedCacheSaving reports false.\n// 3. `thinking` is supported but `budget_tokens` is IGNORED. The request builder\n// enables thinking WITHOUT a budget so no caller is misled into thinking the\n// budget bound the response.\n//\n// PROVENANCE: this profile OBSERVES a third-party route. Everything it reports about\n// DeepSeek's behavior (ignored cache_control, model fallback) is PROVIDER-OBSERVED,\n// never fak-authored — the same OBSERVED-vs-WITNESSED discipline deepseek_pricing.go\n// applies to the billing counters.\n\n// DeepSeekAnthropicMessagesPath is the endpoint SUFFIX the Anthropic Messages wire\n// appends to a base URL. It MUST match internal/agent's anthropicAdapter.Endpoint\n// (`joinEndpoint(baseURL, \"/v1/messages\")`) so the DeepSeek route composes byte-for-byte\n// the way the real-Anthropic route does; deepseek_anthropic_test.go pins the parity.\nconst DeepSeekAnthropicMessagesPath = \"/v1/messages\"\n\n// DeepSeekAnthropicVersion is the anthropic-version header the DeepSeek route sends,\n// matching the value the real-Anthropic adapter uses. DeepSeek's Anthropic-compatible\n// endpoint accepts the same versioned Messages wire.\nconst DeepSeekAnthropicVersion = \"2023-06-01\"\n\n// closed-vocabulary reasons for a REFUSED unsupported content block. A refusal names\n// exactly one of these — never free text — so a caller (or a test) can branch on the\n// reason and an operator sees a stable, greppable token instead of prose.\nconst (\n\tDeepSeekAnthropicUnsupportedImage = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_IMAGE\"\n\tDeepSeekAnthropicUnsupportedDocument = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_DOCUMENT\"\n\tDeepSeekAnthropicUnsupportedRedactedThinking = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_REDACTED_THINKING\"\n\tDeepSeekAnthropicUnsupportedCodeExecution = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_CODE_EXECUTION\"\n\tDeepSeekAnthropicUnsupportedMCP = \"DEEPSEEK_ANTHROPIC_UNSUPPORTED_MCP\"\n)\n\n// deepSeekAnthropicUnsupportedBlocks maps each Anthropic-wire content-block `type`\n// DeepSeek does NOT support to its closed refusal reason. The keys are the documented\n// unsupported set (image, document, redacted thinking, code execution, MCP); a\n// server_tool_use block is DeepSeek's code-execution surface, and mcp_tool_use /\n// mcp_tool_result are the MCP content blocks. Everything NOT in this map is treated\n// as supported (text / tool_use / tool_result / thinking), so a genuinely new block\n// type fails OPEN as supported rather than being wrongly refused — the fence only\n// speaks about blocks the compatibility table names.\nvar deepSeekAnthropicUnsupportedBlocks = map[string]string{\n\t\"image\": DeepSeekAnthropicUnsupportedImage,\n\t\"document\": DeepSeekAnthropicUnsupportedDocument,\n\t\"redacted_thinking\": DeepSeekAnthropicUnsupportedRedactedThinking,\n\t\"code_execution\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\"server_tool_use\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\"mcp_tool_use\": DeepSeekAnthropicUnsupportedMCP,\n\t\"mcp_tool_result\": DeepSeekAnthropicUnsupportedMCP,\n}\n\n// DeepSeekAnthropicUnsupportedBlockReason reports whether a content-block `type` is\n// unsupported on the DeepSeek Anthropic route, and if so its closed refusal reason.\n// The type is normalized (trim + lower) so wire casing does not slip a block past the\n// fence.\nfunc DeepSeekAnthropicUnsupportedBlockReason(blockType string) (reason string, unsupported bool) {\n\tr, ok := deepSeekAnthropicUnsupportedBlocks[strings.ToLower(strings.TrimSpace(blockType))]\n\treturn r, ok\n}\n\n// DeepSeekAnthropicModelSource labels HOW a model id was resolved, so a ledger can tell\n// an operator-chosen direct id apart from a Claude-name mapping fak applied.\ntype DeepSeekAnthropicModelSource string\n\nconst (\n\t// DeepSeekAnthropicModelDirect: the caller already named a real DeepSeek V4 model\n\t// id (deepseek-v4-pro / deepseek-v4-flash). This is the PREFERRED form.\n\tDeepSeekAnthropicModelDirect DeepSeekAnthropicModelSource = \"direct\"\n\t// DeepSeekAnthropicModelClaudeMapped: the caller named a Claude family (opus /\n\t// sonnet / haiku) and fak applied its OWN explicit mapping. This is fak's policy,\n\t// NOT reliance on DeepSeek's server-side \"unknown model → Flash\" fallback.\n\tDeepSeekAnthropicModelClaudeMapped DeepSeekAnthropicModelSource = \"claude-name-mapped:deepseek-coding-agents-guide\"\n)\n\n// ResolveDeepSeekAnthropicModel turns the model id a Claude Code-style client sent into\n// the concrete DeepSeek V4 id fak will put on the wire. It PREFERS a direct\n// deepseek-v4-* id; for a Claude family name it applies fak's OWN explicit mapping\n// (Opus/Sonnet → Pro, Haiku → Flash) taken from DeepSeek's coding-agents guide\n// (https://api-docs.deepseek.com/guides/coding_agents), so the id on the wire is one\n// fak chose rather than leaving DeepSeek to silently coerce an unknown name to Flash.\n//\n// The DeepSeek Claude Code guide sets ANTHROPIC_MODEL=deepseek-v4-pro[1m]; the trailing\n// `[1m]` selects the 1M-context variant. It is stripped for the routing decision (both\n// variants map to the same base id) but reported via has1M so the caller can preserve\n// the marker on the wire if it wants the extended window.\n//\n// It FAILS LOUD (err != nil) on any other name rather than defaulting to Flash — the\n// issue is explicit that DeepSeek's unsupported-model fallback is NOT fak's routing\n// policy.\nfunc ResolveDeepSeekAnthropicModel(requested string) (model string, source DeepSeekAnthropicModelSource, has1M bool, err error) {\n\tnorm := strings.ToLower(strings.TrimSpace(requested))\n\tif strings.HasSuffix(norm, \"[1m]\") {\n\t\thas1M = true\n\t\tnorm = strings.TrimSpace(strings.TrimSuffix(norm, \"[1m]\"))\n\t}\n\tswitch norm {\n\tcase \"\":\n\t\treturn \"\", \"\", has1M, fmt.Errorf(\"deepseek-anthropic: empty model id; name deepseek-v4-pro or deepseek-v4-flash explicitly\")\n\tcase modelroute.DeepSeekV4ProModel:\n\t\treturn modelroute.DeepSeekV4ProModel, DeepSeekAnthropicModelDirect, has1M, nil\n\tcase modelroute.DeepSeekV4FlashModel:\n\t\treturn modelroute.DeepSeekV4FlashModel, DeepSeekAnthropicModelDirect, has1M, nil\n\t}\n\tswitch {\n\tcase strings.Contains(norm, \"opus\"), strings.Contains(norm, \"sonnet\"):\n\t\treturn modelroute.DeepSeekV4ProModel, DeepSeekAnthropicModelClaudeMapped, has1M, nil\n\tcase strings.Contains(norm, \"haiku\"):\n\t\treturn modelroute.DeepSeekV4FlashModel, DeepSeekAnthropicModelClaudeMapped, has1M, nil\n\t}\n\treturn \"\", \"\", has1M, fmt.Errorf(\"deepseek-anthropic: unroutable model %q; name deepseek-v4-pro/deepseek-v4-flash or a Claude opus/sonnet/haiku family (fak will not rely on DeepSeek's silent unknown-model→Flash fallback)\", requested)\n}\n\n// DeepSeek's Anthropic-compatible effort control (`output_config.effort`) accepts these\n// two levels for a coding agent; DeepSeek's guide recommends \"max\" for Claude Code.\nconst (\n\tDeepSeekAnthropicEffortHigh = \"high\"\n\tDeepSeekAnthropicEffortMax = \"max\"\n)\n\n// deepSeekAnthropicWireEfforts is the effort vocabulary this route can put on the wire —\n// the clamp target set for ResolveDeepSeekAnthropicEffort.\nvar deepSeekAnthropicWireEfforts = []string{DeepSeekAnthropicEffortHigh, DeepSeekAnthropicEffortMax}\n\n// ResolveDeepSeekAnthropicEffort maps a CANONICAL fak effort tier onto the two levels this\n// route speaks, SATURATING to the nearest supported tier rather than erroring a request the\n// provider can serve one rung away (#4069). fak's canonical ladder carries rungs DeepSeek\n// simply does not name (none/minimal/low/medium/xhigh), and refusing them made a caller's\n// perfectly ordinary `--effort low` fail the whole request; it now runs at high, and xhigh\n// runs at max (the tie breaks upward — see modelroute.SaturateEffort).\n//\n// The TYPO FENCE this replaced is preserved exactly where it earns its keep. Saturation is\n// only ever applied to a tier fak can PLACE on its ladder; a token off the ladder entirely\n// (\"extreem\") is still refused, because it carries no ordering to clamp with and is far\n// likelier a mistake than an intent. So the route degrades gracefully across a known\n// vocabulary gap while still failing loud on a genuinely unknown one.\n//\n// Returns (\"\", nil) for a blank effort: the control stays unset, which is not an error.\nfunc ResolveDeepSeekAnthropicEffort(effort string) (string, error) {\n\tif strings.TrimSpace(effort) == \"\" {\n\t\treturn \"\", nil\n\t}\n\tif tier := modelroute.SaturateEffort(effort, deepSeekAnthropicWireEfforts); tier != \"\" {\n\t\treturn tier, nil\n\t}\n\treturn \"\", fmt.Errorf(\"deepseek-anthropic: unknown output_config.effort %q; want a canonical effort tier (none/minimal/low/medium/high/xhigh/max), which is saturated to %q or %q\", effort, DeepSeekAnthropicEffortHigh, DeepSeekAnthropicEffortMax)\n}\n\n// ValidateDeepSeekAnthropicEffort reports whether an effort can reach the wire at all: nil\n// for a blank control and for any canonical tier (which ResolveDeepSeekAnthropicEffort\n// saturates into range), an error only for a tier off fak's ladder. Callers that need the\n// tier actually sent should use ResolveDeepSeekAnthropicEffort instead.\nfunc ValidateDeepSeekAnthropicEffort(effort string) error {\n\t_, err := ResolveDeepSeekAnthropicEffort(effort)\n\treturn err\n}\n\n// DeepSeekAnthropicCacheControlProvenance is the provenance label for a cache_control\n// breakpoint sent to the DeepSeek Anthropic route: DeepSeek IGNORES cache_control, so\n// it is a provider no-op, never a fak-authored saving.\nconst DeepSeekAnthropicCacheControlProvenance = \"provider-ignored\"\n\n// IsFakManagedCacheSaving reports whether a cache_control breakpoint on the DeepSeek\n// Anthropic route should be booked as a fak-managed prompt-cache SAVING. It is always\n// false: DeepSeek ignores cache_control, so any implied discount is provider-observed\n// economics (DeepSeek's own default context caching, priced by deepseek_pricing.go),\n// not something a fak mechanism shaped. This is the fence against reporting an ignored\n// cache_control as a fak cache effect.\nfunc IsFakManagedCacheSaving() bool { return false }\n\n// DeepSeekAnthropicProfile is a launch preset that fronts DeepSeek through the\n// Anthropic-compatible route. The zero value is not usable; use\n// NewDeepSeekAnthropicProfile so BaseURL defaults correctly.\ntype DeepSeekAnthropicProfile struct {\n\t// BaseURL is the Anthropic-compatible base; defaults to\n\t// modelroute.DeepSeekAnthropicBaseURL. The Messages path is appended by\n\t// MessagesURL, exactly as the real-Anthropic adapter composes it.\n\tBaseURL string\n\t// APIKey is the DeepSeek API key (the operator-owned DEEPSEEK_API_KEY secret). It\n\t// rides as x-api-key and is NEVER placed in the URL, model id, or body.\n\tAPIKey string\n\t// Model is the DeepSeek V4 id to put on the wire (already resolved via\n\t// ResolveDeepSeekAnthropicModel).\n\tModel string\n\t// Effort, if set, is emitted as output_config.effort. It holds a tier DeepSeek names\n\t// (\"high\"/\"max\"); NewDeepSeekAnthropicProfile has already saturated any other\n\t// canonical tier onto that set (#4069).\n\tEffort string\n}\n\n// NewDeepSeekAnthropicProfile builds a profile, defaulting BaseURL to DeepSeek's\n// documented Anthropic-compatible root and resolving the model id (failing loud on an\n// unroutable name). The [1m] extended-window marker, if the caller sent one, is\n// preserved on the wire model id.\nfunc NewDeepSeekAnthropicProfile(apiKey, requestedModel, effort string) (DeepSeekAnthropicProfile, error) {\n\tmodel, _, has1M, err := ResolveDeepSeekAnthropicModel(requestedModel)\n\tif err != nil {\n\t\treturn DeepSeekAnthropicProfile{}, err\n\t}\n\twireEffort, err := ResolveDeepSeekAnthropicEffort(effort)\n\tif err != nil {\n\t\treturn DeepSeekAnthropicProfile{}, err\n\t}\n\twireModel := model\n\tif has1M {\n\t\twireModel += \"[1m]\"\n\t}\n\treturn DeepSeekAnthropicProfile{\n\t\tBaseURL: modelroute.DeepSeekAnthropicBaseURL,\n\t\tAPIKey: apiKey,\n\t\tModel: wireModel,\n\t\tEffort: wireEffort,\n\t}, nil\n}\n\n// MessagesURL composes the concrete POST target the same way the real-Anthropic route\n// does: the base URL with a trailing slash trimmed, plus the Messages path. With the\n// default base that is https://api.deepseek.com/anthropic/v1/messages.\nfunc (p DeepSeekAnthropicProfile) MessagesURL() string {\n\tbase := p.BaseURL\n\tif base == \"\" {\n\t\tbase = modelroute.DeepSeekAnthropicBaseURL\n\t}\n\treturn strings.TrimRight(base, \"/\") + DeepSeekAnthropicMessagesPath\n}\n\n// headers builds the request headers. The API key rides ONLY as x-api-key (DeepSeek's\n// documented scheme for the Anthropic-compatible route); it never lands in the URL or\n// body, so a request/response log cannot leak it from those surfaces.\nfunc (p DeepSeekAnthropicProfile) headers() map[string]string {\n\th := map[string]string{\n\t\t\"Content-Type\": \"application/json\",\n\t\t\"anthropic-version\": DeepSeekAnthropicVersion,\n\t}\n\tif p.APIKey != \"\" {\n\t\th[\"x-api-key\"] = p.APIKey\n\t}\n\treturn h\n}\n\n// DeepSeekAnthropicBlock is one Anthropic-wire content block on the REQUEST side. Only\n// the `type` is load-bearing for the fence; Text is carried for text blocks.\ntype DeepSeekAnthropicBlock struct {\n\tType string `json:\"type\"`\n\tText string `json:\"text,omitempty\"`\n}\n\n// DeepSeekAnthropicMessage is one role-tagged message with its content blocks.\ntype DeepSeekAnthropicMessage struct {\n\tRole string `json:\"role\"`\n\tContent []DeepSeekAnthropicBlock `json:\"content\"`\n}\n\n// FenceContentBlocks scans every request block and returns an error naming EVERY\n// unsupported block (deduped, sorted, each with its closed reason) so fak fails loud\n// rather than shipping an image/document/redacted-thinking/code-exec/MCP block to a\n// route that would silently drop it. Returns nil when every block is supported.\nfunc FenceContentBlocks(messages []DeepSeekAnthropicMessage) error {\n\tseen := map[string]bool{}\n\tvar refusals []string\n\tfor _, m := range messages {\n\t\tfor _, b := range m.Content {\n\t\t\tif reason, bad := DeepSeekAnthropicUnsupportedBlockReason(b.Type); bad {\n\t\t\t\tkey := strings.ToLower(strings.TrimSpace(b.Type)) + \" \" + reason\n\t\t\t\tif !seen[key] {\n\t\t\t\t\tseen[key] = true\n\t\t\t\t\trefusals = append(refusals, fmt.Sprintf(\"%s(%s)\", strings.ToLower(strings.TrimSpace(b.Type)), reason))\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif len(refusals) == 0 {\n\t\treturn nil\n\t}\n\tsort.Strings(refusals)\n\treturn fmt.Errorf(\"deepseek-anthropic: unsupported content block(s) refused: %s\", strings.Join(refusals, \", \"))\n}\n\n// deepSeekAnthropicWireRequest is the JSON body posted to the Messages endpoint. It\n// enables thinking WITHOUT budget_tokens (DeepSeek ignores it) and emits\n// output_config.effort only when set.\ntype deepSeekAnthropicWireRequest struct {\n\tModel string `json:\"model\"`\n\tMaxTokens int `json:\"max_tokens\"`\n\tSystem string `json:\"system,omitempty\"`\n\tMessages []DeepSeekAnthropicMessage `json:\"messages\"`\n\tThinking *struct {\n\t\tType string `json:\"type\"`\n\t} `json:\"thinking,omitempty\"`\n\tOutputConfig *struct {\n\t\tEffort string `json:\"effort\"`\n\t} `json:\"output_config,omitempty\"`\n}\n\n// DeepSeekAnthropicResponseBlock is one block of the assistant response, covering the\n// text and thinking blocks DeepSeek returns (thinking is supported on this route).\ntype DeepSeekAnthropicResponseBlock struct {\n\tType string `json:\"type\"`\n\tText string `json:\"text,omitempty\"`\n\tThinking string `json:\"thinking,omitempty\"`\n\tSignature string `json:\"signature,omitempty\"`\n}\n\n// DeepSeekAnthropicResponse is the decoded Messages response. Usage carries DeepSeek's\n// own top-level cache hit/miss counters (the OBSERVED numbers deepseek_pricing.go\n// prices), NOT a fak-managed cache saving.\ntype DeepSeekAnthropicResponse struct {\n\tID string `json:\"id\"`\n\tModel string `json:\"model\"`\n\tRole string `json:\"role\"`\n\tContent []DeepSeekAnthropicResponseBlock `json:\"content\"`\n\tStopReason string `json:\"stop_reason\"`\n\tUsage struct {\n\t\tInputTokens int `json:\"input_tokens\"`\n\t\tOutputTokens int `json:\"output_tokens\"`\n\t\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\t\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\t} `json:\"usage\"`\n}\n\n// PostMessages builds the request (fencing unsupported blocks and validating effort),\n// posts it to MessagesURL with the x-api-key credential, and decodes the response. It\n// is the offline-testable seam: pass an *http.Client aimed at an httptest server to\n// exercise the full round trip without a network. thinking enables the thinking block;\n// budget_tokens is intentionally never sent (DeepSeek ignores it).\nfunc (p DeepSeekAnthropicProfile) PostMessages(ctx context.Context, client *http.Client, system string, messages []DeepSeekAnthropicMessage, maxTokens int, thinking bool) (*DeepSeekAnthropicResponse, error) {\n\tif strings.TrimSpace(p.Model) == \"\" {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: profile has no model; build it with NewDeepSeekAnthropicProfile\")\n\t}\n\t// Saturate here too, not just in the constructor: a profile can be hand-built as a\n\t// struct literal, and the tier that reaches the wire must be one DeepSeek names.\n\twireEffort, err := ResolveDeepSeekAnthropicEffort(p.Effort)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif err := FenceContentBlocks(messages); err != nil {\n\t\treturn nil, err\n\t}\n\tbody := deepSeekAnthropicWireRequest{\n\t\tModel: p.Model,\n\t\tMaxTokens: maxTokens,\n\t\tSystem: system,\n\t\tMessages: messages,\n\t}\n\tif thinking {\n\t\tbody.Thinking = &struct {\n\t\t\tType string `json:\"type\"`\n\t\t}{Type: \"enabled\"}\n\t}\n\tif wireEffort != \"\" {\n\t\tbody.OutputConfig = &struct {\n\t\t\tEffort string `json:\"effort\"`\n\t\t}{Effort: wireEffort}\n\t}\n\traw, err := json.Marshal(body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: marshal request: %w\", err)\n\t}\n\treq, err := http.NewRequestWithContext(ctx, http.MethodPost, p.MessagesURL(), bytes.NewReader(raw))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: build request: %w\", err)\n\t}\n\tfor k, v := range p.headers() {\n\t\treq.Header.Set(k, v)\n\t}\n\tif client == nil {\n\t\tclient = &http.Client{Timeout: durEnv(\"FAK_DEEPSEEK_ANTHROPIC_TIMEOUT_S\", 120*time.Second)}\n\t}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: post: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\tpayload, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: read response: %w\", err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: upstream status %d: %s\", resp.StatusCode, strings.TrimSpace(string(payload)))\n\t}\n\tvar out DeepSeekAnthropicResponse\n\tif err := json.Unmarshal(payload, &out); err != nil {\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: decode response: %w\", err)\n\t}\n\treturn &out, nil\n}\n\n// ClaudeCodeDeepSeekEnv is the machine-readable source for the Claude Code runbook\n// (rendered by the human runbook, #3012): the environment a Claude Code-style client\n// sets to route through fak into DeepSeek. The API-key entry is the NAME of the\n// operator-owned secret env var (DEEPSEEK_API_KEY), never a literal secret, so the\n// preset can be printed or committed without leaking a credential. requestedModel is\n// resolved (and validated) so an unroutable name is caught before it reaches a runbook.\nfunc ClaudeCodeDeepSeekEnv(requestedModel string) (map[string]string, error) {\n\tprof, err := NewDeepSeekAnthropicProfile(\"\", requestedModel, \"\")\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn map[string]string{\n\t\t\"ANTHROPIC_BASE_URL\": modelroute.DeepSeekAnthropicBaseURL,\n\t\t\"ANTHROPIC_MODEL\": prof.Model,\n\t\t\"ANTHROPIC_API_KEY\": \"$\" + modelroute.DeepSeekAPIKeyEnv,\n\t\t\"ANTHROPIC_AUTH_KEY\": modelroute.DeepSeekAPIKeyEnv,\n\t}, nil\n}\n\n// ClaudeCodeDeepSeekRunbook renders the env preset as copy-pasteable shell lines for\n// one platform: \"unix\" (bash/zsh `export`) or \"windows\" (PowerShell `$env:`). Both\n// reference the operator-owned DEEPSEEK_API_KEY by name and never inline a secret.\nfunc ClaudeCodeDeepSeekRunbook(platform, requestedModel string) ([]string, error) {\n\tenv, err := ClaudeCodeDeepSeekEnv(requestedModel)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tkeys := make([]string, 0, len(env))\n\tfor k := range env {\n\t\tkeys = append(keys, k)\n\t}\n\tsort.Strings(keys)\n\tvar lines []string\n\tswitch strings.ToLower(strings.TrimSpace(platform)) {\n\tcase \"unix\", \"linux\", \"macos\", \"darwin\":\n\t\tfor _, k := range keys {\n\t\t\tlines = append(lines, fmt.Sprintf(\"export %s=%s\", k, env[k]))\n\t\t}\n\tcase \"windows\", \"powershell\":\n\t\tfor _, k := range keys {\n\t\t\t// PowerShell reads a shell var with $env:NAME; a $-prefixed value from the\n\t\t\t// preset (the secret reference) is rewritten to PowerShell's own form.\n\t\t\tval := env[k]\n\t\t\tif strings.HasPrefix(val, \"$\") {\n\t\t\t\tval = \"$env:\" + strings.TrimPrefix(val, \"$\")\n\t\t\t}\n\t\t\tlines = append(lines, fmt.Sprintf(\"$env:%s = \\\"%s\\\"\", k, val))\n\t\t}\n\tdefault:\n\t\treturn nil, fmt.Errorf(\"deepseek-anthropic: unknown runbook platform %q; want \\\"unix\\\" or \\\"windows\\\"\", platform)\n\t}\n\treturn lines, nil\n}\n"}}, "internal/deepseekbench/deepseekbench.go": {"f1c36fd45ecfb60dc33288bccf3477b30bbd53cf": {"t": 1785745624.3850632, "n": 17829, "h": "dfa43b139ffe14fe", "c": "// Package deepseekbench is the pure core of the DeepSeek V4 Pro/Flash\n// TTFT/TPOT/context-scaling SCORECARD (#3014, under the DeepSeek V4 support\n// program #3006; complements the self-host wire-readiness runbook #3013).\n//\n// It is the CORE half of a wire+core split: this package owns the locked JSONL\n// row schema, the deterministic keyless dry-run fixture, the speedup-refusal\n// honesty gate, and the live streaming measurement — all testable in isolation\n// (`go test ./internal/deepseekbench`), immune to any concurrent shared-tree WIP\n// in cmd/fak. The thin `fak deepseekbench` command in cmd/fak/deepseekbench.go is\n// only the flag/I-O wire over these functions.\n//\n// HONESTY FENCE: this scorecard reports OBSERVED PROVIDER SPEED. A DeepSeek number\n// is never a fak-authored saving — the same OBSERVED-vs-WITNESSED discipline\n// internal/gateway/deepseek_pricing.go applies to the cache counters. CompareSpeedup\n// REFUSES to print a delta unless the two rows share a prompt shape, both carry a\n// verified quality parity, and are both live measurements.\npackage deepseekbench\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Row is ONE scorecard cell. The JSON tags are the locked schema the field-lock\n// test (TestRequiredFields) pins — adding/renaming a field without updating\n// RequiredFields fails the test on purpose.\ntype Row struct {\n\t// Provenance / honesty — read these BEFORE any latency number.\n\tMeasurement string `json:\"measurement\"` // \"dry-run-fixture\" | \"live\"\n\tSpeedProvenance string `json:\"speed_provenance\"` // \"fixture-placeholder-not-measured\" | \"provider-observed\"\n\n\t// Route identity.\n\tModelID string `json:\"model_id\"`\n\tProviderRoute string `json:\"provider_route\"` // e.g. \"deepseek\" | \"anthropic\" | \"openai-compatible\"\n\tEngineRoute string `json:\"engine_route\"` // e.g. \"hosted-api\" | \"vllm\" | \"sglang\"\n\tHosting string `json:\"hosting\"` // \"hosted\" | \"self-hosted\"\n\n\t// Prompt / output shape.\n\tContextBucket string `json:\"context_bucket\"` // 4K | 32K | 128K | 512K | 1M\n\tOutputTarget string `json:\"output_target\"` // short | 1K | 8K | long-reasoning\n\tReasoningMode string `json:\"reasoning_mode\"` // non-thinking | high | max\n\tStream bool `json:\"stream\"`\n\tSpeculative string `json:\"speculative\"` // off | mtp | dspark (#3020)\n\n\t// Latency / throughput.\n\tTTFTMillis float64 `json:\"ttft_ms\"`\n\tTPOTMillis float64 `json:\"tpot_ms\"`\n\tE2EMillis float64 `json:\"e2e_ms\"`\n\tOutputToksPerSec float64 `json:\"output_toks_per_s\"`\n\n\t// Token counters (reasoning_tokens is 0 when the provider does not expose them).\n\tPromptTokens int `json:\"prompt_tokens\"`\n\tCompletionTokens int `json:\"completion_tokens\"`\n\tReasoningTokens int `json:\"reasoning_tokens\"`\n\n\t// Prompt-cache counters + attribution (provider-observed; never fak-authored).\n\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\tCacheAttribution string `json:\"cache_attribution\"` // \"provider-observed\" | \"unknown-dry-run\"\n\n\t// Speculative-decode acceptance evidence (#3020). A string, not a float, so the\n\t// honest default is \"unknown\" when the engine does not expose an acceptance\n\t// counter — mirroring the CacheAttribution provider-observed discipline.\n\tAcceptedTokenRatio string `json:\"accepted_token_ratio\"` // e.g. \"0.87\" | \"unknown\"\n\n\t// Comparability keys — the scorecard refuses a speedup unless these line up.\n\tPromptShapeKey string `json:\"prompt_shape\"` // shape descriptor (bucket|output|reasoning|stream)\n\tQualityParity string `json:\"quality_parity\"` // \"unknown\" | \"verified\" | \"differs\"\n}\n\n// RequiredFields is the locked set of JSON keys every emitted row MUST carry. The\n// field-lock test marshals a row and asserts each key is present (and that the row\n// carries no others), so the issue's required-field list can never silently drift.\nfunc RequiredFields() []string {\n\treturn []string{\n\t\t\"measurement\", \"speed_provenance\",\n\t\t\"model_id\", \"provider_route\", \"engine_route\", \"hosting\",\n\t\t\"context_bucket\", \"output_target\", \"reasoning_mode\", \"stream\", \"speculative\",\n\t\t\"ttft_ms\", \"tpot_ms\", \"e2e_ms\", \"output_toks_per_s\",\n\t\t\"prompt_tokens\", \"completion_tokens\", \"reasoning_tokens\",\n\t\t\"prompt_cache_hit_tokens\", \"prompt_cache_miss_tokens\", \"cache_attribution\",\n\t\t\"accepted_token_ratio\",\n\t\t\"prompt_shape\", \"quality_parity\",\n\t}\n}\n\n// The locked axis vocabularies (the issue's buckets/targets/modes).\nvar (\n\tContextBuckets = []string{\"4K\", \"32K\", \"128K\", \"512K\", \"1M\"}\n\tOutputTargets = []string{\"short\", \"1K\", \"8K\", \"long-reasoning\"}\n\tReasoningModes = []string{\"non-thinking\", \"high\", \"max\"}\n\tSpeculativeModes = []string{\"off\", \"mtp\", \"dspark\"} // #3020\n)\n\n// The two current DeepSeek V4 model ids (both, always, side by side — Flash is\n// never inferred from Pro).\nconst (\n\tModelV4Pro = \"deepseek-v4-pro\"\n\tModelV4Flash = \"deepseek-v4-flash\"\n)\n\n// contextBucketTokens maps a bucket label to its nominal prompt-token count.\nfunc contextBucketTokens(bucket string) int {\n\tswitch bucket {\n\tcase \"4K\":\n\t\treturn 4096\n\tcase \"32K\":\n\t\treturn 32768\n\tcase \"128K\":\n\t\treturn 131072\n\tcase \"512K\":\n\t\treturn 524288\n\tcase \"1M\":\n\t\treturn 1048576\n\t}\n\treturn 0\n}\n\n// outputTargetTokens maps an output target to its nominal completion-token count.\nfunc outputTargetTokens(target string) int {\n\tswitch target {\n\tcase \"short\":\n\t\treturn 64\n\tcase \"1K\":\n\t\treturn 1024\n\tcase \"8K\":\n\t\treturn 8192\n\tcase \"long-reasoning\":\n\t\treturn 16384\n\t}\n\treturn 0\n}\n\n// PromptShape is the comparability key: two rows are comparable only when it matches.\nfunc PromptShape(bucket, output, reasoning string, stream bool) string {\n\treturn fmt.Sprintf(\"%s|%s|%s|stream=%t\", bucket, output, reasoning, stream)\n}\n\n// benchModel is one model's fixture-latency parameters. These are PLACEHOLDERS used\n// only to shape the dry-run fixture; they are NOT measurements and never leave a\n// dry-run row (Measurement stays \"dry-run-fixture\").\ntype benchModel struct {\n\tid string\n\tprefillMsPer float64 // fixture prefill ms per 1K prompt tokens\n\ttpotMs float64 // fixture per-output-token ms\n}\n\n// fixtureModels are V4 Pro and V4 Flash — both, always, side by side. The numbers\n// reflect only the coarse expectation that the 49B-active Pro decodes slower per\n// token than the 13B-active Flash; they are labelled placeholders, not evidence.\nfunc fixtureModels() []benchModel {\n\treturn []benchModel{\n\t\t{id: ModelV4Pro, prefillMsPer: 0.18, tpotMs: 22},\n\t\t{id: ModelV4Flash, prefillMsPer: 0.09, tpotMs: 9},\n\t}\n}\n\n// DryRunRows builds the deterministic no-key fixture: the full model × bucket ×\n// output × reasoning × stream matrix (skipping the contradictory non-thinking +\n// long-reasoning cells), plus one existing-fak-route baseline row so the scorecard\n// has a same-harness comparand to (correctly) REFUSE comparing against in dry-run.\nfunc DryRunRows() []Row {\n\tvar rows []Row\n\tfor _, m := range fixtureModels() {\n\t\tfor _, bucket := range ContextBuckets {\n\t\t\tfor _, output := range OutputTargets {\n\t\t\t\tfor _, reasoning := range ReasoningModes {\n\t\t\t\t\tif output == \"long-reasoning\" && reasoning == \"non-thinking\" {\n\t\t\t\t\t\tcontinue // a non-thinking route does not emit a long reasoning trace\n\t\t\t\t\t}\n\t\t\t\t\tfor _, stream := range []bool{true, false} {\n\t\t\t\t\t\trows = append(rows, fixtureRow(m, bucket, output, reasoning, stream))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// One existing-fak-route baseline row, same harness, same schema. It is also a\n\t// fixture, so the scorecard MUST refuse to compare it (Measurement != live).\n\trows = append(rows, Row{\n\t\tMeasurement: \"dry-run-fixture\",\n\t\tSpeedProvenance: \"fixture-placeholder-not-measured\",\n\t\tModelID: \"claude-sonnet-5\",\n\t\tProviderRoute: \"anthropic\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: \"32K\",\n\t\tOutputTarget: \"1K\",\n\t\tReasoningMode: \"non-thinking\",\n\t\tStream: true,\n\t\tSpeculative: \"off\",\n\t\tCacheAttribution: \"unknown-dry-run\",\n\t\tAcceptedTokenRatio: \"unknown\",\n\t\tPromptShapeKey: PromptShape(\"32K\", \"1K\", \"non-thinking\", true),\n\t\tQualityParity: \"unknown\",\n\t\tPromptTokens: contextBucketTokens(\"32K\"),\n\t\tCompletionTokens: outputTargetTokens(\"1K\"),\n\t})\n\treturn rows\n}\n\n// fixtureRow computes one deterministic placeholder row. No time/rand: every number\n// is a pure function of the axes, so the fixture is byte-stable across runs.\nfunc fixtureRow(m benchModel, bucket, output, reasoning string, stream bool) Row {\n\tprompt := contextBucketTokens(bucket)\n\tcompletion := outputTargetTokens(output)\n\treasoningTokens := 0\n\tswitch reasoning {\n\tcase \"high\":\n\t\treasoningTokens = completion\n\tcase \"max\":\n\t\treasoningTokens = completion * 3\n\t}\n\tgenTokens := completion + reasoningTokens\n\tttft := (float64(prompt) / 1000.0) * m.prefillMsPer\n\ttpot := m.tpotMs\n\tvar e2e, toksPerSec float64\n\tif stream {\n\t\te2e = ttft + tpot*float64(genTokens)\n\t} else {\n\t\t// Non-stream: no incremental token stream, so TPOT is not observable; the whole\n\t\t// generation lands at once and TTFT collapses into E2E.\n\t\ttpot = 0\n\t\te2e = ttft + m.tpotMs*float64(genTokens)\n\t\tttft = e2e\n\t}\n\tif e2e > 0 {\n\t\ttoksPerSec = float64(genTokens) / (e2e / 1000.0)\n\t}\n\treturn Row{\n\t\tMeasurement: \"dry-run-fixture\",\n\t\tSpeedProvenance: \"fixture-placeholder-not-measured\",\n\t\tModelID: m.id,\n\t\tProviderRoute: \"deepseek\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: bucket,\n\t\tOutputTarget: output,\n\t\tReasoningMode: reasoning,\n\t\tStream: stream,\n\t\tSpeculative: \"off\",\n\t\tTTFTMillis: round2(ttft),\n\t\tTPOTMillis: round2(tpot),\n\t\tE2EMillis: round2(e2e),\n\t\tOutputToksPerSec: round2(toksPerSec),\n\t\tPromptTokens: prompt,\n\t\tCompletionTokens: completion,\n\t\tReasoningTokens: reasoningTokens,\n\t\t// The fixture asserts nothing about cache behavior — a real hit/miss split is a\n\t\t// provider-observed number only a live run may fill.\n\t\tPromptCacheHitTokens: 0,\n\t\tPromptCacheMissTokens: 0,\n\t\tCacheAttribution: \"unknown-dry-run\",\n\t\tAcceptedTokenRatio: \"unknown\",\n\t\tPromptShapeKey: PromptShape(bucket, output, reasoning, stream),\n\t\tQualityParity: \"unknown\",\n\t}\n}\n\nfunc round2(v float64) float64 {\n\treturn float64(int64(v*100+0.5)) / 100\n}\n\n// validSpeculative reports whether label is one of the locked SpeculativeModes.\nfunc validSpeculative(label string) bool {\n\tfor _, m := range SpeculativeModes {\n\t\tif label == m {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// CompareSpeedup is the honesty gate the issue demands: it prints a speedup delta\n// ONLY when the DeepSeek row and the baseline row (a) share a prompt shape, (b) both\n// carry a verified quality parity, (c) are both live measurements, and (d) both carry\n// a valid speculative label from SpeculativeModes (#3020). Any other case returns a\n// \"[NOT COMPARABLE: …]\" line and printed=false — a dry-run fixture, a shape mismatch,\n// an unverified parity, or an unlabelled speculative mode can never surface as a\n// speed headline. When a delta IS printed, the subject's accepted_token_ratio rides\n// beside it, so a speculative speedup is never reported without its acceptance\n// evidence (or an honest \"unknown\").\nfunc CompareSpeedup(subject, baseline Row) (line string, printed bool) {\n\tswitch {\n\tcase subject.Measurement != \"live\" || baseline.Measurement != \"live\":\n\t\treturn \"[NOT COMPARABLE: dry-run fixture — no measured latency; run --live with a parity check]\", false\n\tcase subject.PromptShapeKey != baseline.PromptShapeKey:\n\t\treturn fmt.Sprintf(\"[NOT COMPARABLE: prompt shape differs (%s vs %s)]\", subject.PromptShapeKey, baseline.PromptShapeKey), false\n\tcase subject.QualityParity != \"verified\" || baseline.QualityParity != \"verified\":\n\t\treturn \"[NOT COMPARABLE: quality parity not verified for both rows]\", false\n\tcase !validSpeculative(subject.Speculative) || !validSpeculative(baseline.Speculative):\n\t\treturn \"[NOT COMPARABLE: missing speculative label]\", false\n\tcase baseline.E2EMillis <= 0 || subject.E2EMillis <= 0:\n\t\treturn \"[NOT COMPARABLE: a measured E2E is missing]\", false\n\t}\n\tratio := baseline.E2EMillis / subject.E2EMillis\n\treturn fmt.Sprintf(\"OBSERVED provider speed: %s is %.2f× the E2E of %s at shape %s (speculative=%s accepted_token_ratio=%s, provider-observed, not a fak-authored saving)\",\n\t\tsubject.ModelID, ratio, baseline.ModelID, subject.PromptShapeKey, subject.Speculative, subject.AcceptedTokenRatio), true\n}\n\n// LiveGate is the pure opt-in guard for a live run: it admits a live measurement\n// ONLY when a key is present AND spend was explicitly acknowledged, returning the\n// refusal message (and ok=false) otherwise. The wire consults this BEFORE any\n// network call, so default CI — no key, no --spend — never touches the network.\nfunc LiveGate(hasKey, spend bool) (msg string, ok bool) {\n\tif !hasKey {\n\t\treturn \"--live needs DEEPSEEK_API_KEY set (refusing before any network call)\", false\n\t}\n\tif !spend {\n\t\treturn \"--live costs money — pass --spend to acknowledge (refusing)\", false\n\t}\n\treturn \"\", true\n}\n\n// MeasureStreamed issues one streaming chat/completions request against an\n// OpenAI-compatible endpoint (hosted DeepSeek or a self-hosted vLLM/SGLang server)\n// and times TTFT (first content/reasoning delta), TPOT (mean inter-delta), E2E, and\n// reads the final usage block for the token + prompt-cache counters. The http client\n// is injectable so a canned httptest SSE server can witness the parsing/timing logic\n// with no key; production passes a real client. A live row — and only a live row —\n// carries Measurement=\"live\"/SpeedProvenance=\"provider-observed\".\nfunc MeasureStreamed(client *http.Client, baseURL, key, model string) (Row, error) {\n\tif client == nil {\n\t\tclient = &http.Client{Timeout: 5 * time.Minute}\n\t}\n\tbody, _ := json.Marshal(map[string]any{\n\t\t\"model\": model,\n\t\t\"stream\": true,\n\t\t\"messages\": []map[string]string{{\"role\": \"user\", \"content\": \"Reply with a short sentence.\"}},\n\t\t\"stream_options\": map[string]any{\"include_usage\": true},\n\t})\n\turl := strings.TrimRight(baseURL, \"/\") + \"/chat/completions\"\n\treq, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))\n\tif err != nil {\n\t\treturn Row{}, err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tif key != \"\" {\n\t\treq.Header.Set(\"Authorization\", \"Bearer \"+key)\n\t}\n\n\tstart := time.Now()\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn Row{}, err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\tsnippet, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\n\t\treturn Row{}, fmt.Errorf(\"upstream status %d: %s\", resp.StatusCode, strings.TrimSpace(string(snippet)))\n\t}\n\n\tvar ttft time.Duration\n\tvar deltas int\n\tvar lastDelta time.Time\n\tvar interSum time.Duration\n\tprompt, completion, reasoning, hit, miss := 0, 0, 0, 0, 0\n\tsc := bufio.NewScanner(resp.Body)\n\tsc.Buffer(make([]byte, 0, 64*1024), 4*1024*1024)\n\tfor sc.Scan() {\n\t\tline := strings.TrimSpace(sc.Text())\n\t\tif !strings.HasPrefix(line, \"data:\") {\n\t\t\tcontinue\n\t\t}\n\t\tdata := strings.TrimSpace(strings.TrimPrefix(line, \"data:\"))\n\t\tif data == \"[DONE]\" {\n\t\t\tbreak\n\t\t}\n\t\tvar ev struct {\n\t\t\tChoices []struct {\n\t\t\t\tDelta struct {\n\t\t\t\t\tContent string `json:\"content\"`\n\t\t\t\t\tReasoningContent string `json:\"reasoning_content\"`\n\t\t\t\t} `json:\"delta\"`\n\t\t\t} `json:\"choices\"`\n\t\t\tUsage *struct {\n\t\t\t\tPromptTokens int `json:\"prompt_tokens\"`\n\t\t\t\tCompletionTokens int `json:\"completion_tokens\"`\n\t\t\t\tReasoningTokens int `json:\"reasoning_tokens\"`\n\t\t\t\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\t\t\t\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\t\t\t} `json:\"usage\"`\n\t\t}\n\t\tif err := json.Unmarshal([]byte(data), &ev); err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif len(ev.Choices) > 0 && (ev.Choices[0].Delta.Content != \"\" || ev.Choices[0].Delta.ReasoningContent != \"\") {\n\t\t\tnow := time.Now()\n\t\t\tif ttft == 0 {\n\t\t\t\tttft = now.Sub(start)\n\t\t\t} else {\n\t\t\t\tinterSum += now.Sub(lastDelta)\n\t\t\t}\n\t\t\tlastDelta = now\n\t\t\tdeltas++\n\t\t}\n\t\tif ev.Usage != nil {\n\t\t\tprompt = ev.Usage.PromptTokens\n\t\t\tcompletion = ev.Usage.CompletionTokens\n\t\t\treasoning = ev.Usage.ReasoningTokens\n\t\t\thit = ev.Usage.PromptCacheHitTokens\n\t\t\tmiss = ev.Usage.PromptCacheMissTokens\n\t\t}\n\t}\n\tif err := sc.Err(); err != nil {\n\t\treturn Row{}, err\n\t}\n\te2e := time.Since(start)\n\ttpot := 0.0\n\tif deltas > 1 {\n\t\ttpot = float64(interSum.Milliseconds()) / float64(deltas-1)\n\t}\n\ttoksPerSec := 0.0\n\tif e2e > 0 && completion > 0 {\n\t\ttoksPerSec = float64(completion) / e2e.Seconds()\n\t}\n\tattribution := \"unknown\"\n\tif hit > 0 || miss > 0 {\n\t\tattribution = \"provider-observed\"\n\t}\n\treturn Row{\n\t\tMeasurement: \"live\",\n\t\tSpeedProvenance: \"provider-observed\",\n\t\tModelID: model,\n\t\tProviderRoute: \"deepseek\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: \"4K\",\n\t\tOutputTarget: \"short\",\n\t\tReasoningMode: \"non-thinking\",\n\t\tStream: true,\n\t\t// The plain streamed request does not enable a speculative decoder, and the\n\t\t// OpenAI-compatible usage block exposes no acceptance counter — so the row is\n\t\t// honestly labelled off/unknown rather than guessing.\n\t\tSpeculative: \"off\",\n\t\tAcceptedTokenRatio: \"unknown\",\n\t\tTTFTMillis: round2(float64(ttft.Microseconds()) / 1000.0),\n\t\tTPOTMillis: round2(tpot),\n\t\tE2EMillis: round2(float64(e2e.Microseconds()) / 1000.0),\n\t\tOutputToksPerSec: round2(toksPerSec),\n\t\tPromptTokens: prompt,\n\t\tCompletionTokens: completion,\n\t\tReasoningTokens: reasoning,\n\t\tPromptCacheHitTokens: hit,\n\t\tPromptCacheMissTokens: miss,\n\t\tCacheAttribution: attribution,\n\t\tPromptShapeKey: PromptShape(\"4K\", \"short\", \"non-thinking\", true),\n\t\t// A single request cannot assert quality parity against another route.\n\t\tQualityParity: \"unknown\",\n\t}, nil\n}\n"}, "f354231c799d74e9198707be08b24168dcab2d38": {"t": 1785749570.1118, "n": 17829, "h": "dfa43b139ffe14fe", "c": "// Package deepseekbench is the pure core of the DeepSeek V4 Pro/Flash\n// TTFT/TPOT/context-scaling SCORECARD (#3014, under the DeepSeek V4 support\n// program #3006; complements the self-host wire-readiness runbook #3013).\n//\n// It is the CORE half of a wire+core split: this package owns the locked JSONL\n// row schema, the deterministic keyless dry-run fixture, the speedup-refusal\n// honesty gate, and the live streaming measurement — all testable in isolation\n// (`go test ./internal/deepseekbench`), immune to any concurrent shared-tree WIP\n// in cmd/fak. The thin `fak deepseekbench` command in cmd/fak/deepseekbench.go is\n// only the flag/I-O wire over these functions.\n//\n// HONESTY FENCE: this scorecard reports OBSERVED PROVIDER SPEED. A DeepSeek number\n// is never a fak-authored saving — the same OBSERVED-vs-WITNESSED discipline\n// internal/gateway/deepseek_pricing.go applies to the cache counters. CompareSpeedup\n// REFUSES to print a delta unless the two rows share a prompt shape, both carry a\n// verified quality parity, and are both live measurements.\npackage deepseekbench\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Row is ONE scorecard cell. The JSON tags are the locked schema the field-lock\n// test (TestRequiredFields) pins — adding/renaming a field without updating\n// RequiredFields fails the test on purpose.\ntype Row struct {\n\t// Provenance / honesty — read these BEFORE any latency number.\n\tMeasurement string `json:\"measurement\"` // \"dry-run-fixture\" | \"live\"\n\tSpeedProvenance string `json:\"speed_provenance\"` // \"fixture-placeholder-not-measured\" | \"provider-observed\"\n\n\t// Route identity.\n\tModelID string `json:\"model_id\"`\n\tProviderRoute string `json:\"provider_route\"` // e.g. \"deepseek\" | \"anthropic\" | \"openai-compatible\"\n\tEngineRoute string `json:\"engine_route\"` // e.g. \"hosted-api\" | \"vllm\" | \"sglang\"\n\tHosting string `json:\"hosting\"` // \"hosted\" | \"self-hosted\"\n\n\t// Prompt / output shape.\n\tContextBucket string `json:\"context_bucket\"` // 4K | 32K | 128K | 512K | 1M\n\tOutputTarget string `json:\"output_target\"` // short | 1K | 8K | long-reasoning\n\tReasoningMode string `json:\"reasoning_mode\"` // non-thinking | high | max\n\tStream bool `json:\"stream\"`\n\tSpeculative string `json:\"speculative\"` // off | mtp | dspark (#3020)\n\n\t// Latency / throughput.\n\tTTFTMillis float64 `json:\"ttft_ms\"`\n\tTPOTMillis float64 `json:\"tpot_ms\"`\n\tE2EMillis float64 `json:\"e2e_ms\"`\n\tOutputToksPerSec float64 `json:\"output_toks_per_s\"`\n\n\t// Token counters (reasoning_tokens is 0 when the provider does not expose them).\n\tPromptTokens int `json:\"prompt_tokens\"`\n\tCompletionTokens int `json:\"completion_tokens\"`\n\tReasoningTokens int `json:\"reasoning_tokens\"`\n\n\t// Prompt-cache counters + attribution (provider-observed; never fak-authored).\n\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\tCacheAttribution string `json:\"cache_attribution\"` // \"provider-observed\" | \"unknown-dry-run\"\n\n\t// Speculative-decode acceptance evidence (#3020). A string, not a float, so the\n\t// honest default is \"unknown\" when the engine does not expose an acceptance\n\t// counter — mirroring the CacheAttribution provider-observed discipline.\n\tAcceptedTokenRatio string `json:\"accepted_token_ratio\"` // e.g. \"0.87\" | \"unknown\"\n\n\t// Comparability keys — the scorecard refuses a speedup unless these line up.\n\tPromptShapeKey string `json:\"prompt_shape\"` // shape descriptor (bucket|output|reasoning|stream)\n\tQualityParity string `json:\"quality_parity\"` // \"unknown\" | \"verified\" | \"differs\"\n}\n\n// RequiredFields is the locked set of JSON keys every emitted row MUST carry. The\n// field-lock test marshals a row and asserts each key is present (and that the row\n// carries no others), so the issue's required-field list can never silently drift.\nfunc RequiredFields() []string {\n\treturn []string{\n\t\t\"measurement\", \"speed_provenance\",\n\t\t\"model_id\", \"provider_route\", \"engine_route\", \"hosting\",\n\t\t\"context_bucket\", \"output_target\", \"reasoning_mode\", \"stream\", \"speculative\",\n\t\t\"ttft_ms\", \"tpot_ms\", \"e2e_ms\", \"output_toks_per_s\",\n\t\t\"prompt_tokens\", \"completion_tokens\", \"reasoning_tokens\",\n\t\t\"prompt_cache_hit_tokens\", \"prompt_cache_miss_tokens\", \"cache_attribution\",\n\t\t\"accepted_token_ratio\",\n\t\t\"prompt_shape\", \"quality_parity\",\n\t}\n}\n\n// The locked axis vocabularies (the issue's buckets/targets/modes).\nvar (\n\tContextBuckets = []string{\"4K\", \"32K\", \"128K\", \"512K\", \"1M\"}\n\tOutputTargets = []string{\"short\", \"1K\", \"8K\", \"long-reasoning\"}\n\tReasoningModes = []string{\"non-thinking\", \"high\", \"max\"}\n\tSpeculativeModes = []string{\"off\", \"mtp\", \"dspark\"} // #3020\n)\n\n// The two current DeepSeek V4 model ids (both, always, side by side — Flash is\n// never inferred from Pro).\nconst (\n\tModelV4Pro = \"deepseek-v4-pro\"\n\tModelV4Flash = \"deepseek-v4-flash\"\n)\n\n// contextBucketTokens maps a bucket label to its nominal prompt-token count.\nfunc contextBucketTokens(bucket string) int {\n\tswitch bucket {\n\tcase \"4K\":\n\t\treturn 4096\n\tcase \"32K\":\n\t\treturn 32768\n\tcase \"128K\":\n\t\treturn 131072\n\tcase \"512K\":\n\t\treturn 524288\n\tcase \"1M\":\n\t\treturn 1048576\n\t}\n\treturn 0\n}\n\n// outputTargetTokens maps an output target to its nominal completion-token count.\nfunc outputTargetTokens(target string) int {\n\tswitch target {\n\tcase \"short\":\n\t\treturn 64\n\tcase \"1K\":\n\t\treturn 1024\n\tcase \"8K\":\n\t\treturn 8192\n\tcase \"long-reasoning\":\n\t\treturn 16384\n\t}\n\treturn 0\n}\n\n// PromptShape is the comparability key: two rows are comparable only when it matches.\nfunc PromptShape(bucket, output, reasoning string, stream bool) string {\n\treturn fmt.Sprintf(\"%s|%s|%s|stream=%t\", bucket, output, reasoning, stream)\n}\n\n// benchModel is one model's fixture-latency parameters. These are PLACEHOLDERS used\n// only to shape the dry-run fixture; they are NOT measurements and never leave a\n// dry-run row (Measurement stays \"dry-run-fixture\").\ntype benchModel struct {\n\tid string\n\tprefillMsPer float64 // fixture prefill ms per 1K prompt tokens\n\ttpotMs float64 // fixture per-output-token ms\n}\n\n// fixtureModels are V4 Pro and V4 Flash — both, always, side by side. The numbers\n// reflect only the coarse expectation that the 49B-active Pro decodes slower per\n// token than the 13B-active Flash; they are labelled placeholders, not evidence.\nfunc fixtureModels() []benchModel {\n\treturn []benchModel{\n\t\t{id: ModelV4Pro, prefillMsPer: 0.18, tpotMs: 22},\n\t\t{id: ModelV4Flash, prefillMsPer: 0.09, tpotMs: 9},\n\t}\n}\n\n// DryRunRows builds the deterministic no-key fixture: the full model × bucket ×\n// output × reasoning × stream matrix (skipping the contradictory non-thinking +\n// long-reasoning cells), plus one existing-fak-route baseline row so the scorecard\n// has a same-harness comparand to (correctly) REFUSE comparing against in dry-run.\nfunc DryRunRows() []Row {\n\tvar rows []Row\n\tfor _, m := range fixtureModels() {\n\t\tfor _, bucket := range ContextBuckets {\n\t\t\tfor _, output := range OutputTargets {\n\t\t\t\tfor _, reasoning := range ReasoningModes {\n\t\t\t\t\tif output == \"long-reasoning\" && reasoning == \"non-thinking\" {\n\t\t\t\t\t\tcontinue // a non-thinking route does not emit a long reasoning trace\n\t\t\t\t\t}\n\t\t\t\t\tfor _, stream := range []bool{true, false} {\n\t\t\t\t\t\trows = append(rows, fixtureRow(m, bucket, output, reasoning, stream))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// One existing-fak-route baseline row, same harness, same schema. It is also a\n\t// fixture, so the scorecard MUST refuse to compare it (Measurement != live).\n\trows = append(rows, Row{\n\t\tMeasurement: \"dry-run-fixture\",\n\t\tSpeedProvenance: \"fixture-placeholder-not-measured\",\n\t\tModelID: \"claude-sonnet-5\",\n\t\tProviderRoute: \"anthropic\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: \"32K\",\n\t\tOutputTarget: \"1K\",\n\t\tReasoningMode: \"non-thinking\",\n\t\tStream: true,\n\t\tSpeculative: \"off\",\n\t\tCacheAttribution: \"unknown-dry-run\",\n\t\tAcceptedTokenRatio: \"unknown\",\n\t\tPromptShapeKey: PromptShape(\"32K\", \"1K\", \"non-thinking\", true),\n\t\tQualityParity: \"unknown\",\n\t\tPromptTokens: contextBucketTokens(\"32K\"),\n\t\tCompletionTokens: outputTargetTokens(\"1K\"),\n\t})\n\treturn rows\n}\n\n// fixtureRow computes one deterministic placeholder row. No time/rand: every number\n// is a pure function of the axes, so the fixture is byte-stable across runs.\nfunc fixtureRow(m benchModel, bucket, output, reasoning string, stream bool) Row {\n\tprompt := contextBucketTokens(bucket)\n\tcompletion := outputTargetTokens(output)\n\treasoningTokens := 0\n\tswitch reasoning {\n\tcase \"high\":\n\t\treasoningTokens = completion\n\tcase \"max\":\n\t\treasoningTokens = completion * 3\n\t}\n\tgenTokens := completion + reasoningTokens\n\tttft := (float64(prompt) / 1000.0) * m.prefillMsPer\n\ttpot := m.tpotMs\n\tvar e2e, toksPerSec float64\n\tif stream {\n\t\te2e = ttft + tpot*float64(genTokens)\n\t} else {\n\t\t// Non-stream: no incremental token stream, so TPOT is not observable; the whole\n\t\t// generation lands at once and TTFT collapses into E2E.\n\t\ttpot = 0\n\t\te2e = ttft + m.tpotMs*float64(genTokens)\n\t\tttft = e2e\n\t}\n\tif e2e > 0 {\n\t\ttoksPerSec = float64(genTokens) / (e2e / 1000.0)\n\t}\n\treturn Row{\n\t\tMeasurement: \"dry-run-fixture\",\n\t\tSpeedProvenance: \"fixture-placeholder-not-measured\",\n\t\tModelID: m.id,\n\t\tProviderRoute: \"deepseek\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: bucket,\n\t\tOutputTarget: output,\n\t\tReasoningMode: reasoning,\n\t\tStream: stream,\n\t\tSpeculative: \"off\",\n\t\tTTFTMillis: round2(ttft),\n\t\tTPOTMillis: round2(tpot),\n\t\tE2EMillis: round2(e2e),\n\t\tOutputToksPerSec: round2(toksPerSec),\n\t\tPromptTokens: prompt,\n\t\tCompletionTokens: completion,\n\t\tReasoningTokens: reasoningTokens,\n\t\t// The fixture asserts nothing about cache behavior — a real hit/miss split is a\n\t\t// provider-observed number only a live run may fill.\n\t\tPromptCacheHitTokens: 0,\n\t\tPromptCacheMissTokens: 0,\n\t\tCacheAttribution: \"unknown-dry-run\",\n\t\tAcceptedTokenRatio: \"unknown\",\n\t\tPromptShapeKey: PromptShape(bucket, output, reasoning, stream),\n\t\tQualityParity: \"unknown\",\n\t}\n}\n\nfunc round2(v float64) float64 {\n\treturn float64(int64(v*100+0.5)) / 100\n}\n\n// validSpeculative reports whether label is one of the locked SpeculativeModes.\nfunc validSpeculative(label string) bool {\n\tfor _, m := range SpeculativeModes {\n\t\tif label == m {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// CompareSpeedup is the honesty gate the issue demands: it prints a speedup delta\n// ONLY when the DeepSeek row and the baseline row (a) share a prompt shape, (b) both\n// carry a verified quality parity, (c) are both live measurements, and (d) both carry\n// a valid speculative label from SpeculativeModes (#3020). Any other case returns a\n// \"[NOT COMPARABLE: …]\" line and printed=false — a dry-run fixture, a shape mismatch,\n// an unverified parity, or an unlabelled speculative mode can never surface as a\n// speed headline. When a delta IS printed, the subject's accepted_token_ratio rides\n// beside it, so a speculative speedup is never reported without its acceptance\n// evidence (or an honest \"unknown\").\nfunc CompareSpeedup(subject, baseline Row) (line string, printed bool) {\n\tswitch {\n\tcase subject.Measurement != \"live\" || baseline.Measurement != \"live\":\n\t\treturn \"[NOT COMPARABLE: dry-run fixture — no measured latency; run --live with a parity check]\", false\n\tcase subject.PromptShapeKey != baseline.PromptShapeKey:\n\t\treturn fmt.Sprintf(\"[NOT COMPARABLE: prompt shape differs (%s vs %s)]\", subject.PromptShapeKey, baseline.PromptShapeKey), false\n\tcase subject.QualityParity != \"verified\" || baseline.QualityParity != \"verified\":\n\t\treturn \"[NOT COMPARABLE: quality parity not verified for both rows]\", false\n\tcase !validSpeculative(subject.Speculative) || !validSpeculative(baseline.Speculative):\n\t\treturn \"[NOT COMPARABLE: missing speculative label]\", false\n\tcase baseline.E2EMillis <= 0 || subject.E2EMillis <= 0:\n\t\treturn \"[NOT COMPARABLE: a measured E2E is missing]\", false\n\t}\n\tratio := baseline.E2EMillis / subject.E2EMillis\n\treturn fmt.Sprintf(\"OBSERVED provider speed: %s is %.2f× the E2E of %s at shape %s (speculative=%s accepted_token_ratio=%s, provider-observed, not a fak-authored saving)\",\n\t\tsubject.ModelID, ratio, baseline.ModelID, subject.PromptShapeKey, subject.Speculative, subject.AcceptedTokenRatio), true\n}\n\n// LiveGate is the pure opt-in guard for a live run: it admits a live measurement\n// ONLY when a key is present AND spend was explicitly acknowledged, returning the\n// refusal message (and ok=false) otherwise. The wire consults this BEFORE any\n// network call, so default CI — no key, no --spend — never touches the network.\nfunc LiveGate(hasKey, spend bool) (msg string, ok bool) {\n\tif !hasKey {\n\t\treturn \"--live needs DEEPSEEK_API_KEY set (refusing before any network call)\", false\n\t}\n\tif !spend {\n\t\treturn \"--live costs money — pass --spend to acknowledge (refusing)\", false\n\t}\n\treturn \"\", true\n}\n\n// MeasureStreamed issues one streaming chat/completions request against an\n// OpenAI-compatible endpoint (hosted DeepSeek or a self-hosted vLLM/SGLang server)\n// and times TTFT (first content/reasoning delta), TPOT (mean inter-delta), E2E, and\n// reads the final usage block for the token + prompt-cache counters. The http client\n// is injectable so a canned httptest SSE server can witness the parsing/timing logic\n// with no key; production passes a real client. A live row — and only a live row —\n// carries Measurement=\"live\"/SpeedProvenance=\"provider-observed\".\nfunc MeasureStreamed(client *http.Client, baseURL, key, model string) (Row, error) {\n\tif client == nil {\n\t\tclient = &http.Client{Timeout: 5 * time.Minute}\n\t}\n\tbody, _ := json.Marshal(map[string]any{\n\t\t\"model\": model,\n\t\t\"stream\": true,\n\t\t\"messages\": []map[string]string{{\"role\": \"user\", \"content\": \"Reply with a short sentence.\"}},\n\t\t\"stream_options\": map[string]any{\"include_usage\": true},\n\t})\n\turl := strings.TrimRight(baseURL, \"/\") + \"/chat/completions\"\n\treq, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))\n\tif err != nil {\n\t\treturn Row{}, err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tif key != \"\" {\n\t\treq.Header.Set(\"Authorization\", \"Bearer \"+key)\n\t}\n\n\tstart := time.Now()\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn Row{}, err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\tsnippet, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\n\t\treturn Row{}, fmt.Errorf(\"upstream status %d: %s\", resp.StatusCode, strings.TrimSpace(string(snippet)))\n\t}\n\n\tvar ttft time.Duration\n\tvar deltas int\n\tvar lastDelta time.Time\n\tvar interSum time.Duration\n\tprompt, completion, reasoning, hit, miss := 0, 0, 0, 0, 0\n\tsc := bufio.NewScanner(resp.Body)\n\tsc.Buffer(make([]byte, 0, 64*1024), 4*1024*1024)\n\tfor sc.Scan() {\n\t\tline := strings.TrimSpace(sc.Text())\n\t\tif !strings.HasPrefix(line, \"data:\") {\n\t\t\tcontinue\n\t\t}\n\t\tdata := strings.TrimSpace(strings.TrimPrefix(line, \"data:\"))\n\t\tif data == \"[DONE]\" {\n\t\t\tbreak\n\t\t}\n\t\tvar ev struct {\n\t\t\tChoices []struct {\n\t\t\t\tDelta struct {\n\t\t\t\t\tContent string `json:\"content\"`\n\t\t\t\t\tReasoningContent string `json:\"reasoning_content\"`\n\t\t\t\t} `json:\"delta\"`\n\t\t\t} `json:\"choices\"`\n\t\t\tUsage *struct {\n\t\t\t\tPromptTokens int `json:\"prompt_tokens\"`\n\t\t\t\tCompletionTokens int `json:\"completion_tokens\"`\n\t\t\t\tReasoningTokens int `json:\"reasoning_tokens\"`\n\t\t\t\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\t\t\t\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\t\t\t} `json:\"usage\"`\n\t\t}\n\t\tif err := json.Unmarshal([]byte(data), &ev); err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif len(ev.Choices) > 0 && (ev.Choices[0].Delta.Content != \"\" || ev.Choices[0].Delta.ReasoningContent != \"\") {\n\t\t\tnow := time.Now()\n\t\t\tif ttft == 0 {\n\t\t\t\tttft = now.Sub(start)\n\t\t\t} else {\n\t\t\t\tinterSum += now.Sub(lastDelta)\n\t\t\t}\n\t\t\tlastDelta = now\n\t\t\tdeltas++\n\t\t}\n\t\tif ev.Usage != nil {\n\t\t\tprompt = ev.Usage.PromptTokens\n\t\t\tcompletion = ev.Usage.CompletionTokens\n\t\t\treasoning = ev.Usage.ReasoningTokens\n\t\t\thit = ev.Usage.PromptCacheHitTokens\n\t\t\tmiss = ev.Usage.PromptCacheMissTokens\n\t\t}\n\t}\n\tif err := sc.Err(); err != nil {\n\t\treturn Row{}, err\n\t}\n\te2e := time.Since(start)\n\ttpot := 0.0\n\tif deltas > 1 {\n\t\ttpot = float64(interSum.Milliseconds()) / float64(deltas-1)\n\t}\n\ttoksPerSec := 0.0\n\tif e2e > 0 && completion > 0 {\n\t\ttoksPerSec = float64(completion) / e2e.Seconds()\n\t}\n\tattribution := \"unknown\"\n\tif hit > 0 || miss > 0 {\n\t\tattribution = \"provider-observed\"\n\t}\n\treturn Row{\n\t\tMeasurement: \"live\",\n\t\tSpeedProvenance: \"provider-observed\",\n\t\tModelID: model,\n\t\tProviderRoute: \"deepseek\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: \"4K\",\n\t\tOutputTarget: \"short\",\n\t\tReasoningMode: \"non-thinking\",\n\t\tStream: true,\n\t\t// The plain streamed request does not enable a speculative decoder, and the\n\t\t// OpenAI-compatible usage block exposes no acceptance counter — so the row is\n\t\t// honestly labelled off/unknown rather than guessing.\n\t\tSpeculative: \"off\",\n\t\tAcceptedTokenRatio: \"unknown\",\n\t\tTTFTMillis: round2(float64(ttft.Microseconds()) / 1000.0),\n\t\tTPOTMillis: round2(tpot),\n\t\tE2EMillis: round2(float64(e2e.Microseconds()) / 1000.0),\n\t\tOutputToksPerSec: round2(toksPerSec),\n\t\tPromptTokens: prompt,\n\t\tCompletionTokens: completion,\n\t\tReasoningTokens: reasoning,\n\t\tPromptCacheHitTokens: hit,\n\t\tPromptCacheMissTokens: miss,\n\t\tCacheAttribution: attribution,\n\t\tPromptShapeKey: PromptShape(\"4K\", \"short\", \"non-thinking\", true),\n\t\t// A single request cannot assert quality parity against another route.\n\t\tQualityParity: \"unknown\",\n\t}, nil\n}\n"}, "40a12d1deecdd6f16523c3a7b7d7f7ff711f5b86": {"t": 1785749571.230883, "n": 16194, "h": "34858e4da4e515c3", "c": "// Package deepseekbench is the pure core of the DeepSeek V4 Pro/Flash\n// TTFT/TPOT/context-scaling SCORECARD (#3014, under the DeepSeek V4 support\n// program #3006; complements the self-host wire-readiness runbook #3013).\n//\n// It is the CORE half of a wire+core split: this package owns the locked JSONL\n// row schema, the deterministic keyless dry-run fixture, the speedup-refusal\n// honesty gate, and the live streaming measurement — all testable in isolation\n// (`go test ./internal/deepseekbench`), immune to any concurrent shared-tree WIP\n// in cmd/fak. The thin `fak deepseekbench` command in cmd/fak/deepseekbench.go is\n// only the flag/I-O wire over these functions.\n//\n// HONESTY FENCE: this scorecard reports OBSERVED PROVIDER SPEED. A DeepSeek number\n// is never a fak-authored saving — the same OBSERVED-vs-WITNESSED discipline\n// internal/gateway/deepseek_pricing.go applies to the cache counters. CompareSpeedup\n// REFUSES to print a delta unless the two rows share a prompt shape, both carry a\n// verified quality parity, and are both live measurements.\npackage deepseekbench\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Row is ONE scorecard cell. The JSON tags are the locked schema the field-lock\n// test (TestRequiredFields) pins — adding/renaming a field without updating\n// RequiredFields fails the test on purpose.\ntype Row struct {\n\t// Provenance / honesty — read these BEFORE any latency number.\n\tMeasurement string `json:\"measurement\"` // \"dry-run-fixture\" | \"live\"\n\tSpeedProvenance string `json:\"speed_provenance\"` // \"fixture-placeholder-not-measured\" | \"provider-observed\"\n\n\t// Route identity.\n\tModelID string `json:\"model_id\"`\n\tProviderRoute string `json:\"provider_route\"` // e.g. \"deepseek\" | \"anthropic\" | \"openai-compatible\"\n\tEngineRoute string `json:\"engine_route\"` // e.g. \"hosted-api\" | \"vllm\" | \"sglang\"\n\tHosting string `json:\"hosting\"` // \"hosted\" | \"self-hosted\"\n\n\t// Prompt / output shape.\n\tContextBucket string `json:\"context_bucket\"` // 4K | 32K | 128K | 512K | 1M\n\tOutputTarget string `json:\"output_target\"` // short | 1K | 8K | long-reasoning\n\tReasoningMode string `json:\"reasoning_mode\"` // non-thinking | high | max\n\tStream bool `json:\"stream\"`\n\n\t// Latency / throughput.\n\tTTFTMillis float64 `json:\"ttft_ms\"`\n\tTPOTMillis float64 `json:\"tpot_ms\"`\n\tE2EMillis float64 `json:\"e2e_ms\"`\n\tOutputToksPerSec float64 `json:\"output_toks_per_s\"`\n\n\t// Token counters (reasoning_tokens is 0 when the provider does not expose them).\n\tPromptTokens int `json:\"prompt_tokens\"`\n\tCompletionTokens int `json:\"completion_tokens\"`\n\tReasoningTokens int `json:\"reasoning_tokens\"`\n\n\t// Prompt-cache counters + attribution (provider-observed; never fak-authored).\n\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\tCacheAttribution string `json:\"cache_attribution\"` // \"provider-observed\" | \"unknown-dry-run\"\n\n\t// Comparability keys — the scorecard refuses a speedup unless these line up.\n\tPromptShapeKey string `json:\"prompt_shape\"` // shape descriptor (bucket|output|reasoning|stream)\n\tQualityParity string `json:\"quality_parity\"` // \"unknown\" | \"verified\" | \"differs\"\n}\n\n// RequiredFields is the locked set of JSON keys every emitted row MUST carry. The\n// field-lock test marshals a row and asserts each key is present (and that the row\n// carries no others), so the issue's required-field list can never silently drift.\nfunc RequiredFields() []string {\n\treturn []string{\n\t\t\"measurement\", \"speed_provenance\",\n\t\t\"model_id\", \"provider_route\", \"engine_route\", \"hosting\",\n\t\t\"context_bucket\", \"output_target\", \"reasoning_mode\", \"stream\",\n\t\t\"ttft_ms\", \"tpot_ms\", \"e2e_ms\", \"output_toks_per_s\",\n\t\t\"prompt_tokens\", \"completion_tokens\", \"reasoning_tokens\",\n\t\t\"prompt_cache_hit_tokens\", \"prompt_cache_miss_tokens\", \"cache_attribution\",\n\t\t\"prompt_shape\", \"quality_parity\",\n\t}\n}\n\n// The locked axis vocabularies (the issue's buckets/targets/modes).\nvar (\n\tContextBuckets = []string{\"4K\", \"32K\", \"128K\", \"512K\", \"1M\"}\n\tOutputTargets = []string{\"short\", \"1K\", \"8K\", \"long-reasoning\"}\n\tReasoningModes = []string{\"non-thinking\", \"high\", \"max\"}\n)\n\n// The two current DeepSeek V4 model ids (both, always, side by side — Flash is\n// never inferred from Pro).\nconst (\n\tModelV4Pro = \"deepseek-v4-pro\"\n\tModelV4Flash = \"deepseek-v4-flash\"\n)\n\n// contextBucketTokens maps a bucket label to its nominal prompt-token count.\nfunc contextBucketTokens(bucket string) int {\n\tswitch bucket {\n\tcase \"4K\":\n\t\treturn 4096\n\tcase \"32K\":\n\t\treturn 32768\n\tcase \"128K\":\n\t\treturn 131072\n\tcase \"512K\":\n\t\treturn 524288\n\tcase \"1M\":\n\t\treturn 1048576\n\t}\n\treturn 0\n}\n\n// outputTargetTokens maps an output target to its nominal completion-token count.\nfunc outputTargetTokens(target string) int {\n\tswitch target {\n\tcase \"short\":\n\t\treturn 64\n\tcase \"1K\":\n\t\treturn 1024\n\tcase \"8K\":\n\t\treturn 8192\n\tcase \"long-reasoning\":\n\t\treturn 16384\n\t}\n\treturn 0\n}\n\n// PromptShape is the comparability key: two rows are comparable only when it matches.\nfunc PromptShape(bucket, output, reasoning string, stream bool) string {\n\treturn fmt.Sprintf(\"%s|%s|%s|stream=%t\", bucket, output, reasoning, stream)\n}\n\n// benchModel is one model's fixture-latency parameters. These are PLACEHOLDERS used\n// only to shape the dry-run fixture; they are NOT measurements and never leave a\n// dry-run row (Measurement stays \"dry-run-fixture\").\ntype benchModel struct {\n\tid string\n\tprefillMsPer float64 // fixture prefill ms per 1K prompt tokens\n\ttpotMs float64 // fixture per-output-token ms\n}\n\n// fixtureModels are V4 Pro and V4 Flash — both, always, side by side. The numbers\n// reflect only the coarse expectation that the 49B-active Pro decodes slower per\n// token than the 13B-active Flash; they are labelled placeholders, not evidence.\nfunc fixtureModels() []benchModel {\n\treturn []benchModel{\n\t\t{id: ModelV4Pro, prefillMsPer: 0.18, tpotMs: 22},\n\t\t{id: ModelV4Flash, prefillMsPer: 0.09, tpotMs: 9},\n\t}\n}\n\n// DryRunRows builds the deterministic no-key fixture: the full model × bucket ×\n// output × reasoning × stream matrix (skipping the contradictory non-thinking +\n// long-reasoning cells), plus one existing-fak-route baseline row so the scorecard\n// has a same-harness comparand to (correctly) REFUSE comparing against in dry-run.\nfunc DryRunRows() []Row {\n\tvar rows []Row\n\tfor _, m := range fixtureModels() {\n\t\tfor _, bucket := range ContextBuckets {\n\t\t\tfor _, output := range OutputTargets {\n\t\t\t\tfor _, reasoning := range ReasoningModes {\n\t\t\t\t\tif output == \"long-reasoning\" && reasoning == \"non-thinking\" {\n\t\t\t\t\t\tcontinue // a non-thinking route does not emit a long reasoning trace\n\t\t\t\t\t}\n\t\t\t\t\tfor _, stream := range []bool{true, false} {\n\t\t\t\t\t\trows = append(rows, fixtureRow(m, bucket, output, reasoning, stream))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// One existing-fak-route baseline row, same harness, same schema. It is also a\n\t// fixture, so the scorecard MUST refuse to compare it (Measurement != live).\n\trows = append(rows, Row{\n\t\tMeasurement: \"dry-run-fixture\",\n\t\tSpeedProvenance: \"fixture-placeholder-not-measured\",\n\t\tModelID: \"claude-sonnet-5\",\n\t\tProviderRoute: \"anthropic\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: \"32K\",\n\t\tOutputTarget: \"1K\",\n\t\tReasoningMode: \"non-thinking\",\n\t\tStream: true,\n\t\tCacheAttribution: \"unknown-dry-run\",\n\t\tPromptShapeKey: PromptShape(\"32K\", \"1K\", \"non-thinking\", true),\n\t\tQualityParity: \"unknown\",\n\t\tPromptTokens: contextBucketTokens(\"32K\"),\n\t\tCompletionTokens: outputTargetTokens(\"1K\"),\n\t})\n\treturn rows\n}\n\n// fixtureRow computes one deterministic placeholder row. No time/rand: every number\n// is a pure function of the axes, so the fixture is byte-stable across runs.\nfunc fixtureRow(m benchModel, bucket, output, reasoning string, stream bool) Row {\n\tprompt := contextBucketTokens(bucket)\n\tcompletion := outputTargetTokens(output)\n\treasoningTokens := 0\n\tswitch reasoning {\n\tcase \"high\":\n\t\treasoningTokens = completion\n\tcase \"max\":\n\t\treasoningTokens = completion * 3\n\t}\n\tgenTokens := completion + reasoningTokens\n\tttft := (float64(prompt) / 1000.0) * m.prefillMsPer\n\ttpot := m.tpotMs\n\tvar e2e, toksPerSec float64\n\tif stream {\n\t\te2e = ttft + tpot*float64(genTokens)\n\t} else {\n\t\t// Non-stream: no incremental token stream, so TPOT is not observable; the whole\n\t\t// generation lands at once and TTFT collapses into E2E.\n\t\ttpot = 0\n\t\te2e = ttft + m.tpotMs*float64(genTokens)\n\t\tttft = e2e\n\t}\n\tif e2e > 0 {\n\t\ttoksPerSec = float64(genTokens) / (e2e / 1000.0)\n\t}\n\treturn Row{\n\t\tMeasurement: \"dry-run-fixture\",\n\t\tSpeedProvenance: \"fixture-placeholder-not-measured\",\n\t\tModelID: m.id,\n\t\tProviderRoute: \"deepseek\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: bucket,\n\t\tOutputTarget: output,\n\t\tReasoningMode: reasoning,\n\t\tStream: stream,\n\t\tTTFTMillis: round2(ttft),\n\t\tTPOTMillis: round2(tpot),\n\t\tE2EMillis: round2(e2e),\n\t\tOutputToksPerSec: round2(toksPerSec),\n\t\tPromptTokens: prompt,\n\t\tCompletionTokens: completion,\n\t\tReasoningTokens: reasoningTokens,\n\t\t// The fixture asserts nothing about cache behavior — a real hit/miss split is a\n\t\t// provider-observed number only a live run may fill.\n\t\tPromptCacheHitTokens: 0,\n\t\tPromptCacheMissTokens: 0,\n\t\tCacheAttribution: \"unknown-dry-run\",\n\t\tPromptShapeKey: PromptShape(bucket, output, reasoning, stream),\n\t\tQualityParity: \"unknown\",\n\t}\n}\n\nfunc round2(v float64) float64 {\n\treturn float64(int64(v*100+0.5)) / 100\n}\n\n// CompareSpeedup is the honesty gate the issue demands: it prints a speedup delta\n// ONLY when the DeepSeek row and the baseline row (a) share a prompt shape, (b) both\n// carry a verified quality parity, and (c) are both live measurements. Any other case\n// returns a \"[NOT COMPARABLE: …]\" line and printed=false — a dry-run fixture, a shape\n// mismatch, or an unverified parity can never surface as a speed headline.\nfunc CompareSpeedup(subject, baseline Row) (line string, printed bool) {\n\tswitch {\n\tcase subject.Measurement != \"live\" || baseline.Measurement != \"live\":\n\t\treturn \"[NOT COMPARABLE: dry-run fixture — no measured latency; run --live with a parity check]\", false\n\tcase subject.PromptShapeKey != baseline.PromptShapeKey:\n\t\treturn fmt.Sprintf(\"[NOT COMPARABLE: prompt shape differs (%s vs %s)]\", subject.PromptShapeKey, baseline.PromptShapeKey), false\n\tcase subject.QualityParity != \"verified\" || baseline.QualityParity != \"verified\":\n\t\treturn \"[NOT COMPARABLE: quality parity not verified for both rows]\", false\n\tcase baseline.E2EMillis <= 0 || subject.E2EMillis <= 0:\n\t\treturn \"[NOT COMPARABLE: a measured E2E is missing]\", false\n\t}\n\tratio := baseline.E2EMillis / subject.E2EMillis\n\treturn fmt.Sprintf(\"OBSERVED provider speed: %s is %.2f× the E2E of %s at shape %s (provider-observed, not a fak-authored saving)\",\n\t\tsubject.ModelID, ratio, baseline.ModelID, subject.PromptShapeKey), true\n}\n\n// LiveGate is the pure opt-in guard for a live run: it admits a live measurement\n// ONLY when a key is present AND spend was explicitly acknowledged, returning the\n// refusal message (and ok=false) otherwise. The wire consults this BEFORE any\n// network call, so default CI — no key, no --spend — never touches the network.\nfunc LiveGate(hasKey, spend bool) (msg string, ok bool) {\n\tif !hasKey {\n\t\treturn \"--live needs DEEPSEEK_API_KEY set (refusing before any network call)\", false\n\t}\n\tif !spend {\n\t\treturn \"--live costs money — pass --spend to acknowledge (refusing)\", false\n\t}\n\treturn \"\", true\n}\n\n// MeasureStreamed issues one streaming chat/completions request against an\n// OpenAI-compatible endpoint (hosted DeepSeek or a self-hosted vLLM/SGLang server)\n// and times TTFT (first content/reasoning delta), TPOT (mean inter-delta), E2E, and\n// reads the final usage block for the token + prompt-cache counters. The http client\n// is injectable so a canned httptest SSE server can witness the parsing/timing logic\n// with no key; production passes a real client. A live row — and only a live row —\n// carries Measurement=\"live\"/SpeedProvenance=\"provider-observed\".\nfunc MeasureStreamed(client *http.Client, baseURL, key, model string) (Row, error) {\n\tif client == nil {\n\t\tclient = &http.Client{Timeout: 5 * time.Minute}\n\t}\n\tbody, _ := json.Marshal(map[string]any{\n\t\t\"model\": model,\n\t\t\"stream\": true,\n\t\t\"messages\": []map[string]string{{\"role\": \"user\", \"content\": \"Reply with a short sentence.\"}},\n\t\t\"stream_options\": map[string]any{\"include_usage\": true},\n\t})\n\turl := strings.TrimRight(baseURL, \"/\") + \"/chat/completions\"\n\treq, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))\n\tif err != nil {\n\t\treturn Row{}, err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tif key != \"\" {\n\t\treq.Header.Set(\"Authorization\", \"Bearer \"+key)\n\t}\n\n\tstart := time.Now()\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn Row{}, err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\tsnippet, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\n\t\treturn Row{}, fmt.Errorf(\"upstream status %d: %s\", resp.StatusCode, strings.TrimSpace(string(snippet)))\n\t}\n\n\tvar ttft time.Duration\n\tvar deltas int\n\tvar lastDelta time.Time\n\tvar interSum time.Duration\n\tprompt, completion, reasoning, hit, miss := 0, 0, 0, 0, 0\n\tsc := bufio.NewScanner(resp.Body)\n\tsc.Buffer(make([]byte, 0, 64*1024), 4*1024*1024)\n\tfor sc.Scan() {\n\t\tline := strings.TrimSpace(sc.Text())\n\t\tif !strings.HasPrefix(line, \"data:\") {\n\t\t\tcontinue\n\t\t}\n\t\tdata := strings.TrimSpace(strings.TrimPrefix(line, \"data:\"))\n\t\tif data == \"[DONE]\" {\n\t\t\tbreak\n\t\t}\n\t\tvar ev struct {\n\t\t\tChoices []struct {\n\t\t\t\tDelta struct {\n\t\t\t\t\tContent string `json:\"content\"`\n\t\t\t\t\tReasoningContent string `json:\"reasoning_content\"`\n\t\t\t\t} `json:\"delta\"`\n\t\t\t} `json:\"choices\"`\n\t\t\tUsage *struct {\n\t\t\t\tPromptTokens int `json:\"prompt_tokens\"`\n\t\t\t\tCompletionTokens int `json:\"completion_tokens\"`\n\t\t\t\tReasoningTokens int `json:\"reasoning_tokens\"`\n\t\t\t\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\t\t\t\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\t\t\t} `json:\"usage\"`\n\t\t}\n\t\tif err := json.Unmarshal([]byte(data), &ev); err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif len(ev.Choices) > 0 && (ev.Choices[0].Delta.Content != \"\" || ev.Choices[0].Delta.ReasoningContent != \"\") {\n\t\t\tnow := time.Now()\n\t\t\tif ttft == 0 {\n\t\t\t\tttft = now.Sub(start)\n\t\t\t} else {\n\t\t\t\tinterSum += now.Sub(lastDelta)\n\t\t\t}\n\t\t\tlastDelta = now\n\t\t\tdeltas++\n\t\t}\n\t\tif ev.Usage != nil {\n\t\t\tprompt = ev.Usage.PromptTokens\n\t\t\tcompletion = ev.Usage.CompletionTokens\n\t\t\treasoning = ev.Usage.ReasoningTokens\n\t\t\thit = ev.Usage.PromptCacheHitTokens\n\t\t\tmiss = ev.Usage.PromptCacheMissTokens\n\t\t}\n\t}\n\tif err := sc.Err(); err != nil {\n\t\treturn Row{}, err\n\t}\n\te2e := time.Since(start)\n\ttpot := 0.0\n\tif deltas > 1 {\n\t\ttpot = float64(interSum.Milliseconds()) / float64(deltas-1)\n\t}\n\ttoksPerSec := 0.0\n\tif e2e > 0 && completion > 0 {\n\t\ttoksPerSec = float64(completion) / e2e.Seconds()\n\t}\n\tattribution := \"unknown\"\n\tif hit > 0 || miss > 0 {\n\t\tattribution = \"provider-observed\"\n\t}\n\treturn Row{\n\t\tMeasurement: \"live\",\n\t\tSpeedProvenance: \"provider-observed\",\n\t\tModelID: model,\n\t\tProviderRoute: \"deepseek\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: \"4K\",\n\t\tOutputTarget: \"short\",\n\t\tReasoningMode: \"non-thinking\",\n\t\tStream: true,\n\t\tTTFTMillis: round2(float64(ttft.Microseconds()) / 1000.0),\n\t\tTPOTMillis: round2(tpot),\n\t\tE2EMillis: round2(float64(e2e.Microseconds()) / 1000.0),\n\t\tOutputToksPerSec: round2(toksPerSec),\n\t\tPromptTokens: prompt,\n\t\tCompletionTokens: completion,\n\t\tReasoningTokens: reasoning,\n\t\tPromptCacheHitTokens: hit,\n\t\tPromptCacheMissTokens: miss,\n\t\tCacheAttribution: attribution,\n\t\tPromptShapeKey: PromptShape(\"4K\", \"short\", \"non-thinking\", true),\n\t\t// A single request cannot assert quality parity against another route.\n\t\tQualityParity: \"unknown\",\n\t}, nil\n}\n"}, "13f5f1a6e0391eb28bcbea759890fb009251e5d1": {"t": 1786067420.1152372, "n": 17829, "h": "dfa43b139ffe14fe", "c": "// Package deepseekbench is the pure core of the DeepSeek V4 Pro/Flash\n// TTFT/TPOT/context-scaling SCORECARD (#3014, under the DeepSeek V4 support\n// program #3006; complements the self-host wire-readiness runbook #3013).\n//\n// It is the CORE half of a wire+core split: this package owns the locked JSONL\n// row schema, the deterministic keyless dry-run fixture, the speedup-refusal\n// honesty gate, and the live streaming measurement — all testable in isolation\n// (`go test ./internal/deepseekbench`), immune to any concurrent shared-tree WIP\n// in cmd/fak. The thin `fak deepseekbench` command in cmd/fak/deepseekbench.go is\n// only the flag/I-O wire over these functions.\n//\n// HONESTY FENCE: this scorecard reports OBSERVED PROVIDER SPEED. A DeepSeek number\n// is never a fak-authored saving — the same OBSERVED-vs-WITNESSED discipline\n// internal/gateway/deepseek_pricing.go applies to the cache counters. CompareSpeedup\n// REFUSES to print a delta unless the two rows share a prompt shape, both carry a\n// verified quality parity, and are both live measurements.\npackage deepseekbench\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Row is ONE scorecard cell. The JSON tags are the locked schema the field-lock\n// test (TestRequiredFields) pins — adding/renaming a field without updating\n// RequiredFields fails the test on purpose.\ntype Row struct {\n\t// Provenance / honesty — read these BEFORE any latency number.\n\tMeasurement string `json:\"measurement\"` // \"dry-run-fixture\" | \"live\"\n\tSpeedProvenance string `json:\"speed_provenance\"` // \"fixture-placeholder-not-measured\" | \"provider-observed\"\n\n\t// Route identity.\n\tModelID string `json:\"model_id\"`\n\tProviderRoute string `json:\"provider_route\"` // e.g. \"deepseek\" | \"anthropic\" | \"openai-compatible\"\n\tEngineRoute string `json:\"engine_route\"` // e.g. \"hosted-api\" | \"vllm\" | \"sglang\"\n\tHosting string `json:\"hosting\"` // \"hosted\" | \"self-hosted\"\n\n\t// Prompt / output shape.\n\tContextBucket string `json:\"context_bucket\"` // 4K | 32K | 128K | 512K | 1M\n\tOutputTarget string `json:\"output_target\"` // short | 1K | 8K | long-reasoning\n\tReasoningMode string `json:\"reasoning_mode\"` // non-thinking | high | max\n\tStream bool `json:\"stream\"`\n\tSpeculative string `json:\"speculative\"` // off | mtp | dspark (#3020)\n\n\t// Latency / throughput.\n\tTTFTMillis float64 `json:\"ttft_ms\"`\n\tTPOTMillis float64 `json:\"tpot_ms\"`\n\tE2EMillis float64 `json:\"e2e_ms\"`\n\tOutputToksPerSec float64 `json:\"output_toks_per_s\"`\n\n\t// Token counters (reasoning_tokens is 0 when the provider does not expose them).\n\tPromptTokens int `json:\"prompt_tokens\"`\n\tCompletionTokens int `json:\"completion_tokens\"`\n\tReasoningTokens int `json:\"reasoning_tokens\"`\n\n\t// Prompt-cache counters + attribution (provider-observed; never fak-authored).\n\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\tCacheAttribution string `json:\"cache_attribution\"` // \"provider-observed\" | \"unknown-dry-run\"\n\n\t// Speculative-decode acceptance evidence (#3020). A string, not a float, so the\n\t// honest default is \"unknown\" when the engine does not expose an acceptance\n\t// counter — mirroring the CacheAttribution provider-observed discipline.\n\tAcceptedTokenRatio string `json:\"accepted_token_ratio\"` // e.g. \"0.87\" | \"unknown\"\n\n\t// Comparability keys — the scorecard refuses a speedup unless these line up.\n\tPromptShapeKey string `json:\"prompt_shape\"` // shape descriptor (bucket|output|reasoning|stream)\n\tQualityParity string `json:\"quality_parity\"` // \"unknown\" | \"verified\" | \"differs\"\n}\n\n// RequiredFields is the locked set of JSON keys every emitted row MUST carry. The\n// field-lock test marshals a row and asserts each key is present (and that the row\n// carries no others), so the issue's required-field list can never silently drift.\nfunc RequiredFields() []string {\n\treturn []string{\n\t\t\"measurement\", \"speed_provenance\",\n\t\t\"model_id\", \"provider_route\", \"engine_route\", \"hosting\",\n\t\t\"context_bucket\", \"output_target\", \"reasoning_mode\", \"stream\", \"speculative\",\n\t\t\"ttft_ms\", \"tpot_ms\", \"e2e_ms\", \"output_toks_per_s\",\n\t\t\"prompt_tokens\", \"completion_tokens\", \"reasoning_tokens\",\n\t\t\"prompt_cache_hit_tokens\", \"prompt_cache_miss_tokens\", \"cache_attribution\",\n\t\t\"accepted_token_ratio\",\n\t\t\"prompt_shape\", \"quality_parity\",\n\t}\n}\n\n// The locked axis vocabularies (the issue's buckets/targets/modes).\nvar (\n\tContextBuckets = []string{\"4K\", \"32K\", \"128K\", \"512K\", \"1M\"}\n\tOutputTargets = []string{\"short\", \"1K\", \"8K\", \"long-reasoning\"}\n\tReasoningModes = []string{\"non-thinking\", \"high\", \"max\"}\n\tSpeculativeModes = []string{\"off\", \"mtp\", \"dspark\"} // #3020\n)\n\n// The two current DeepSeek V4 model ids (both, always, side by side — Flash is\n// never inferred from Pro).\nconst (\n\tModelV4Pro = \"deepseek-v4-pro\"\n\tModelV4Flash = \"deepseek-v4-flash\"\n)\n\n// contextBucketTokens maps a bucket label to its nominal prompt-token count.\nfunc contextBucketTokens(bucket string) int {\n\tswitch bucket {\n\tcase \"4K\":\n\t\treturn 4096\n\tcase \"32K\":\n\t\treturn 32768\n\tcase \"128K\":\n\t\treturn 131072\n\tcase \"512K\":\n\t\treturn 524288\n\tcase \"1M\":\n\t\treturn 1048576\n\t}\n\treturn 0\n}\n\n// outputTargetTokens maps an output target to its nominal completion-token count.\nfunc outputTargetTokens(target string) int {\n\tswitch target {\n\tcase \"short\":\n\t\treturn 64\n\tcase \"1K\":\n\t\treturn 1024\n\tcase \"8K\":\n\t\treturn 8192\n\tcase \"long-reasoning\":\n\t\treturn 16384\n\t}\n\treturn 0\n}\n\n// PromptShape is the comparability key: two rows are comparable only when it matches.\nfunc PromptShape(bucket, output, reasoning string, stream bool) string {\n\treturn fmt.Sprintf(\"%s|%s|%s|stream=%t\", bucket, output, reasoning, stream)\n}\n\n// benchModel is one model's fixture-latency parameters. These are PLACEHOLDERS used\n// only to shape the dry-run fixture; they are NOT measurements and never leave a\n// dry-run row (Measurement stays \"dry-run-fixture\").\ntype benchModel struct {\n\tid string\n\tprefillMsPer float64 // fixture prefill ms per 1K prompt tokens\n\ttpotMs float64 // fixture per-output-token ms\n}\n\n// fixtureModels are V4 Pro and V4 Flash — both, always, side by side. The numbers\n// reflect only the coarse expectation that the 49B-active Pro decodes slower per\n// token than the 13B-active Flash; they are labelled placeholders, not evidence.\nfunc fixtureModels() []benchModel {\n\treturn []benchModel{\n\t\t{id: ModelV4Pro, prefillMsPer: 0.18, tpotMs: 22},\n\t\t{id: ModelV4Flash, prefillMsPer: 0.09, tpotMs: 9},\n\t}\n}\n\n// DryRunRows builds the deterministic no-key fixture: the full model × bucket ×\n// output × reasoning × stream matrix (skipping the contradictory non-thinking +\n// long-reasoning cells), plus one existing-fak-route baseline row so the scorecard\n// has a same-harness comparand to (correctly) REFUSE comparing against in dry-run.\nfunc DryRunRows() []Row {\n\tvar rows []Row\n\tfor _, m := range fixtureModels() {\n\t\tfor _, bucket := range ContextBuckets {\n\t\t\tfor _, output := range OutputTargets {\n\t\t\t\tfor _, reasoning := range ReasoningModes {\n\t\t\t\t\tif output == \"long-reasoning\" && reasoning == \"non-thinking\" {\n\t\t\t\t\t\tcontinue // a non-thinking route does not emit a long reasoning trace\n\t\t\t\t\t}\n\t\t\t\t\tfor _, stream := range []bool{true, false} {\n\t\t\t\t\t\trows = append(rows, fixtureRow(m, bucket, output, reasoning, stream))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// One existing-fak-route baseline row, same harness, same schema. It is also a\n\t// fixture, so the scorecard MUST refuse to compare it (Measurement != live).\n\trows = append(rows, Row{\n\t\tMeasurement: \"dry-run-fixture\",\n\t\tSpeedProvenance: \"fixture-placeholder-not-measured\",\n\t\tModelID: \"claude-sonnet-5\",\n\t\tProviderRoute: \"anthropic\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: \"32K\",\n\t\tOutputTarget: \"1K\",\n\t\tReasoningMode: \"non-thinking\",\n\t\tStream: true,\n\t\tSpeculative: \"off\",\n\t\tCacheAttribution: \"unknown-dry-run\",\n\t\tAcceptedTokenRatio: \"unknown\",\n\t\tPromptShapeKey: PromptShape(\"32K\", \"1K\", \"non-thinking\", true),\n\t\tQualityParity: \"unknown\",\n\t\tPromptTokens: contextBucketTokens(\"32K\"),\n\t\tCompletionTokens: outputTargetTokens(\"1K\"),\n\t})\n\treturn rows\n}\n\n// fixtureRow computes one deterministic placeholder row. No time/rand: every number\n// is a pure function of the axes, so the fixture is byte-stable across runs.\nfunc fixtureRow(m benchModel, bucket, output, reasoning string, stream bool) Row {\n\tprompt := contextBucketTokens(bucket)\n\tcompletion := outputTargetTokens(output)\n\treasoningTokens := 0\n\tswitch reasoning {\n\tcase \"high\":\n\t\treasoningTokens = completion\n\tcase \"max\":\n\t\treasoningTokens = completion * 3\n\t}\n\tgenTokens := completion + reasoningTokens\n\tttft := (float64(prompt) / 1000.0) * m.prefillMsPer\n\ttpot := m.tpotMs\n\tvar e2e, toksPerSec float64\n\tif stream {\n\t\te2e = ttft + tpot*float64(genTokens)\n\t} else {\n\t\t// Non-stream: no incremental token stream, so TPOT is not observable; the whole\n\t\t// generation lands at once and TTFT collapses into E2E.\n\t\ttpot = 0\n\t\te2e = ttft + m.tpotMs*float64(genTokens)\n\t\tttft = e2e\n\t}\n\tif e2e > 0 {\n\t\ttoksPerSec = float64(genTokens) / (e2e / 1000.0)\n\t}\n\treturn Row{\n\t\tMeasurement: \"dry-run-fixture\",\n\t\tSpeedProvenance: \"fixture-placeholder-not-measured\",\n\t\tModelID: m.id,\n\t\tProviderRoute: \"deepseek\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: bucket,\n\t\tOutputTarget: output,\n\t\tReasoningMode: reasoning,\n\t\tStream: stream,\n\t\tSpeculative: \"off\",\n\t\tTTFTMillis: round2(ttft),\n\t\tTPOTMillis: round2(tpot),\n\t\tE2EMillis: round2(e2e),\n\t\tOutputToksPerSec: round2(toksPerSec),\n\t\tPromptTokens: prompt,\n\t\tCompletionTokens: completion,\n\t\tReasoningTokens: reasoningTokens,\n\t\t// The fixture asserts nothing about cache behavior — a real hit/miss split is a\n\t\t// provider-observed number only a live run may fill.\n\t\tPromptCacheHitTokens: 0,\n\t\tPromptCacheMissTokens: 0,\n\t\tCacheAttribution: \"unknown-dry-run\",\n\t\tAcceptedTokenRatio: \"unknown\",\n\t\tPromptShapeKey: PromptShape(bucket, output, reasoning, stream),\n\t\tQualityParity: \"unknown\",\n\t}\n}\n\nfunc round2(v float64) float64 {\n\treturn float64(int64(v*100+0.5)) / 100\n}\n\n// validSpeculative reports whether label is one of the locked SpeculativeModes.\nfunc validSpeculative(label string) bool {\n\tfor _, m := range SpeculativeModes {\n\t\tif label == m {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// CompareSpeedup is the honesty gate the issue demands: it prints a speedup delta\n// ONLY when the DeepSeek row and the baseline row (a) share a prompt shape, (b) both\n// carry a verified quality parity, (c) are both live measurements, and (d) both carry\n// a valid speculative label from SpeculativeModes (#3020). Any other case returns a\n// \"[NOT COMPARABLE: …]\" line and printed=false — a dry-run fixture, a shape mismatch,\n// an unverified parity, or an unlabelled speculative mode can never surface as a\n// speed headline. When a delta IS printed, the subject's accepted_token_ratio rides\n// beside it, so a speculative speedup is never reported without its acceptance\n// evidence (or an honest \"unknown\").\nfunc CompareSpeedup(subject, baseline Row) (line string, printed bool) {\n\tswitch {\n\tcase subject.Measurement != \"live\" || baseline.Measurement != \"live\":\n\t\treturn \"[NOT COMPARABLE: dry-run fixture — no measured latency; run --live with a parity check]\", false\n\tcase subject.PromptShapeKey != baseline.PromptShapeKey:\n\t\treturn fmt.Sprintf(\"[NOT COMPARABLE: prompt shape differs (%s vs %s)]\", subject.PromptShapeKey, baseline.PromptShapeKey), false\n\tcase subject.QualityParity != \"verified\" || baseline.QualityParity != \"verified\":\n\t\treturn \"[NOT COMPARABLE: quality parity not verified for both rows]\", false\n\tcase !validSpeculative(subject.Speculative) || !validSpeculative(baseline.Speculative):\n\t\treturn \"[NOT COMPARABLE: missing speculative label]\", false\n\tcase baseline.E2EMillis <= 0 || subject.E2EMillis <= 0:\n\t\treturn \"[NOT COMPARABLE: a measured E2E is missing]\", false\n\t}\n\tratio := baseline.E2EMillis / subject.E2EMillis\n\treturn fmt.Sprintf(\"OBSERVED provider speed: %s is %.2f× the E2E of %s at shape %s (speculative=%s accepted_token_ratio=%s, provider-observed, not a fak-authored saving)\",\n\t\tsubject.ModelID, ratio, baseline.ModelID, subject.PromptShapeKey, subject.Speculative, subject.AcceptedTokenRatio), true\n}\n\n// LiveGate is the pure opt-in guard for a live run: it admits a live measurement\n// ONLY when a key is present AND spend was explicitly acknowledged, returning the\n// refusal message (and ok=false) otherwise. The wire consults this BEFORE any\n// network call, so default CI — no key, no --spend — never touches the network.\nfunc LiveGate(hasKey, spend bool) (msg string, ok bool) {\n\tif !hasKey {\n\t\treturn \"--live needs DEEPSEEK_API_KEY set (refusing before any network call)\", false\n\t}\n\tif !spend {\n\t\treturn \"--live costs money — pass --spend to acknowledge (refusing)\", false\n\t}\n\treturn \"\", true\n}\n\n// MeasureStreamed issues one streaming chat/completions request against an\n// OpenAI-compatible endpoint (hosted DeepSeek or a self-hosted vLLM/SGLang server)\n// and times TTFT (first content/reasoning delta), TPOT (mean inter-delta), E2E, and\n// reads the final usage block for the token + prompt-cache counters. The http client\n// is injectable so a canned httptest SSE server can witness the parsing/timing logic\n// with no key; production passes a real client. A live row — and only a live row —\n// carries Measurement=\"live\"/SpeedProvenance=\"provider-observed\".\nfunc MeasureStreamed(client *http.Client, baseURL, key, model string) (Row, error) {\n\tif client == nil {\n\t\tclient = &http.Client{Timeout: 5 * time.Minute}\n\t}\n\tbody, _ := json.Marshal(map[string]any{\n\t\t\"model\": model,\n\t\t\"stream\": true,\n\t\t\"messages\": []map[string]string{{\"role\": \"user\", \"content\": \"Reply with a short sentence.\"}},\n\t\t\"stream_options\": map[string]any{\"include_usage\": true},\n\t})\n\turl := strings.TrimRight(baseURL, \"/\") + \"/chat/completions\"\n\treq, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))\n\tif err != nil {\n\t\treturn Row{}, err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tif key != \"\" {\n\t\treq.Header.Set(\"Authorization\", \"Bearer \"+key)\n\t}\n\n\tstart := time.Now()\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn Row{}, err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\tsnippet, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\n\t\treturn Row{}, fmt.Errorf(\"upstream status %d: %s\", resp.StatusCode, strings.TrimSpace(string(snippet)))\n\t}\n\n\tvar ttft time.Duration\n\tvar deltas int\n\tvar lastDelta time.Time\n\tvar interSum time.Duration\n\tprompt, completion, reasoning, hit, miss := 0, 0, 0, 0, 0\n\tsc := bufio.NewScanner(resp.Body)\n\tsc.Buffer(make([]byte, 0, 64*1024), 4*1024*1024)\n\tfor sc.Scan() {\n\t\tline := strings.TrimSpace(sc.Text())\n\t\tif !strings.HasPrefix(line, \"data:\") {\n\t\t\tcontinue\n\t\t}\n\t\tdata := strings.TrimSpace(strings.TrimPrefix(line, \"data:\"))\n\t\tif data == \"[DONE]\" {\n\t\t\tbreak\n\t\t}\n\t\tvar ev struct {\n\t\t\tChoices []struct {\n\t\t\t\tDelta struct {\n\t\t\t\t\tContent string `json:\"content\"`\n\t\t\t\t\tReasoningContent string `json:\"reasoning_content\"`\n\t\t\t\t} `json:\"delta\"`\n\t\t\t} `json:\"choices\"`\n\t\t\tUsage *struct {\n\t\t\t\tPromptTokens int `json:\"prompt_tokens\"`\n\t\t\t\tCompletionTokens int `json:\"completion_tokens\"`\n\t\t\t\tReasoningTokens int `json:\"reasoning_tokens\"`\n\t\t\t\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\t\t\t\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\t\t\t} `json:\"usage\"`\n\t\t}\n\t\tif err := json.Unmarshal([]byte(data), &ev); err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif len(ev.Choices) > 0 && (ev.Choices[0].Delta.Content != \"\" || ev.Choices[0].Delta.ReasoningContent != \"\") {\n\t\t\tnow := time.Now()\n\t\t\tif ttft == 0 {\n\t\t\t\tttft = now.Sub(start)\n\t\t\t} else {\n\t\t\t\tinterSum += now.Sub(lastDelta)\n\t\t\t}\n\t\t\tlastDelta = now\n\t\t\tdeltas++\n\t\t}\n\t\tif ev.Usage != nil {\n\t\t\tprompt = ev.Usage.PromptTokens\n\t\t\tcompletion = ev.Usage.CompletionTokens\n\t\t\treasoning = ev.Usage.ReasoningTokens\n\t\t\thit = ev.Usage.PromptCacheHitTokens\n\t\t\tmiss = ev.Usage.PromptCacheMissTokens\n\t\t}\n\t}\n\tif err := sc.Err(); err != nil {\n\t\treturn Row{}, err\n\t}\n\te2e := time.Since(start)\n\ttpot := 0.0\n\tif deltas > 1 {\n\t\ttpot = float64(interSum.Milliseconds()) / float64(deltas-1)\n\t}\n\ttoksPerSec := 0.0\n\tif e2e > 0 && completion > 0 {\n\t\ttoksPerSec = float64(completion) / e2e.Seconds()\n\t}\n\tattribution := \"unknown\"\n\tif hit > 0 || miss > 0 {\n\t\tattribution = \"provider-observed\"\n\t}\n\treturn Row{\n\t\tMeasurement: \"live\",\n\t\tSpeedProvenance: \"provider-observed\",\n\t\tModelID: model,\n\t\tProviderRoute: \"deepseek\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: \"4K\",\n\t\tOutputTarget: \"short\",\n\t\tReasoningMode: \"non-thinking\",\n\t\tStream: true,\n\t\t// The plain streamed request does not enable a speculative decoder, and the\n\t\t// OpenAI-compatible usage block exposes no acceptance counter — so the row is\n\t\t// honestly labelled off/unknown rather than guessing.\n\t\tSpeculative: \"off\",\n\t\tAcceptedTokenRatio: \"unknown\",\n\t\tTTFTMillis: round2(float64(ttft.Microseconds()) / 1000.0),\n\t\tTPOTMillis: round2(tpot),\n\t\tE2EMillis: round2(float64(e2e.Microseconds()) / 1000.0),\n\t\tOutputToksPerSec: round2(toksPerSec),\n\t\tPromptTokens: prompt,\n\t\tCompletionTokens: completion,\n\t\tReasoningTokens: reasoning,\n\t\tPromptCacheHitTokens: hit,\n\t\tPromptCacheMissTokens: miss,\n\t\tCacheAttribution: attribution,\n\t\tPromptShapeKey: PromptShape(\"4K\", \"short\", \"non-thinking\", true),\n\t\t// A single request cannot assert quality parity against another route.\n\t\tQualityParity: \"unknown\",\n\t}, nil\n}\n"}, "c99f9d32ca81593f379f2688d676f9071cefe2df": {"t": 1786068285.0742147, "n": 17829, "h": "dfa43b139ffe14fe", "c": "// Package deepseekbench is the pure core of the DeepSeek V4 Pro/Flash\n// TTFT/TPOT/context-scaling SCORECARD (#3014, under the DeepSeek V4 support\n// program #3006; complements the self-host wire-readiness runbook #3013).\n//\n// It is the CORE half of a wire+core split: this package owns the locked JSONL\n// row schema, the deterministic keyless dry-run fixture, the speedup-refusal\n// honesty gate, and the live streaming measurement — all testable in isolation\n// (`go test ./internal/deepseekbench`), immune to any concurrent shared-tree WIP\n// in cmd/fak. The thin `fak deepseekbench` command in cmd/fak/deepseekbench.go is\n// only the flag/I-O wire over these functions.\n//\n// HONESTY FENCE: this scorecard reports OBSERVED PROVIDER SPEED. A DeepSeek number\n// is never a fak-authored saving — the same OBSERVED-vs-WITNESSED discipline\n// internal/gateway/deepseek_pricing.go applies to the cache counters. CompareSpeedup\n// REFUSES to print a delta unless the two rows share a prompt shape, both carry a\n// verified quality parity, and are both live measurements.\npackage deepseekbench\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"net/http\"\n\t\"strings\"\n\t\"time\"\n)\n\n// Row is ONE scorecard cell. The JSON tags are the locked schema the field-lock\n// test (TestRequiredFields) pins — adding/renaming a field without updating\n// RequiredFields fails the test on purpose.\ntype Row struct {\n\t// Provenance / honesty — read these BEFORE any latency number.\n\tMeasurement string `json:\"measurement\"` // \"dry-run-fixture\" | \"live\"\n\tSpeedProvenance string `json:\"speed_provenance\"` // \"fixture-placeholder-not-measured\" | \"provider-observed\"\n\n\t// Route identity.\n\tModelID string `json:\"model_id\"`\n\tProviderRoute string `json:\"provider_route\"` // e.g. \"deepseek\" | \"anthropic\" | \"openai-compatible\"\n\tEngineRoute string `json:\"engine_route\"` // e.g. \"hosted-api\" | \"vllm\" | \"sglang\"\n\tHosting string `json:\"hosting\"` // \"hosted\" | \"self-hosted\"\n\n\t// Prompt / output shape.\n\tContextBucket string `json:\"context_bucket\"` // 4K | 32K | 128K | 512K | 1M\n\tOutputTarget string `json:\"output_target\"` // short | 1K | 8K | long-reasoning\n\tReasoningMode string `json:\"reasoning_mode\"` // non-thinking | high | max\n\tStream bool `json:\"stream\"`\n\tSpeculative string `json:\"speculative\"` // off | mtp | dspark (#3020)\n\n\t// Latency / throughput.\n\tTTFTMillis float64 `json:\"ttft_ms\"`\n\tTPOTMillis float64 `json:\"tpot_ms\"`\n\tE2EMillis float64 `json:\"e2e_ms\"`\n\tOutputToksPerSec float64 `json:\"output_toks_per_s\"`\n\n\t// Token counters (reasoning_tokens is 0 when the provider does not expose them).\n\tPromptTokens int `json:\"prompt_tokens\"`\n\tCompletionTokens int `json:\"completion_tokens\"`\n\tReasoningTokens int `json:\"reasoning_tokens\"`\n\n\t// Prompt-cache counters + attribution (provider-observed; never fak-authored).\n\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\tCacheAttribution string `json:\"cache_attribution\"` // \"provider-observed\" | \"unknown-dry-run\"\n\n\t// Speculative-decode acceptance evidence (#3020). A string, not a float, so the\n\t// honest default is \"unknown\" when the engine does not expose an acceptance\n\t// counter — mirroring the CacheAttribution provider-observed discipline.\n\tAcceptedTokenRatio string `json:\"accepted_token_ratio\"` // e.g. \"0.87\" | \"unknown\"\n\n\t// Comparability keys — the scorecard refuses a speedup unless these line up.\n\tPromptShapeKey string `json:\"prompt_shape\"` // shape descriptor (bucket|output|reasoning|stream)\n\tQualityParity string `json:\"quality_parity\"` // \"unknown\" | \"verified\" | \"differs\"\n}\n\n// RequiredFields is the locked set of JSON keys every emitted row MUST carry. The\n// field-lock test marshals a row and asserts each key is present (and that the row\n// carries no others), so the issue's required-field list can never silently drift.\nfunc RequiredFields() []string {\n\treturn []string{\n\t\t\"measurement\", \"speed_provenance\",\n\t\t\"model_id\", \"provider_route\", \"engine_route\", \"hosting\",\n\t\t\"context_bucket\", \"output_target\", \"reasoning_mode\", \"stream\", \"speculative\",\n\t\t\"ttft_ms\", \"tpot_ms\", \"e2e_ms\", \"output_toks_per_s\",\n\t\t\"prompt_tokens\", \"completion_tokens\", \"reasoning_tokens\",\n\t\t\"prompt_cache_hit_tokens\", \"prompt_cache_miss_tokens\", \"cache_attribution\",\n\t\t\"accepted_token_ratio\",\n\t\t\"prompt_shape\", \"quality_parity\",\n\t}\n}\n\n// The locked axis vocabularies (the issue's buckets/targets/modes).\nvar (\n\tContextBuckets = []string{\"4K\", \"32K\", \"128K\", \"512K\", \"1M\"}\n\tOutputTargets = []string{\"short\", \"1K\", \"8K\", \"long-reasoning\"}\n\tReasoningModes = []string{\"non-thinking\", \"high\", \"max\"}\n\tSpeculativeModes = []string{\"off\", \"mtp\", \"dspark\"} // #3020\n)\n\n// The two current DeepSeek V4 model ids (both, always, side by side — Flash is\n// never inferred from Pro).\nconst (\n\tModelV4Pro = \"deepseek-v4-pro\"\n\tModelV4Flash = \"deepseek-v4-flash\"\n)\n\n// contextBucketTokens maps a bucket label to its nominal prompt-token count.\nfunc contextBucketTokens(bucket string) int {\n\tswitch bucket {\n\tcase \"4K\":\n\t\treturn 4096\n\tcase \"32K\":\n\t\treturn 32768\n\tcase \"128K\":\n\t\treturn 131072\n\tcase \"512K\":\n\t\treturn 524288\n\tcase \"1M\":\n\t\treturn 1048576\n\t}\n\treturn 0\n}\n\n// outputTargetTokens maps an output target to its nominal completion-token count.\nfunc outputTargetTokens(target string) int {\n\tswitch target {\n\tcase \"short\":\n\t\treturn 64\n\tcase \"1K\":\n\t\treturn 1024\n\tcase \"8K\":\n\t\treturn 8192\n\tcase \"long-reasoning\":\n\t\treturn 16384\n\t}\n\treturn 0\n}\n\n// PromptShape is the comparability key: two rows are comparable only when it matches.\nfunc PromptShape(bucket, output, reasoning string, stream bool) string {\n\treturn fmt.Sprintf(\"%s|%s|%s|stream=%t\", bucket, output, reasoning, stream)\n}\n\n// benchModel is one model's fixture-latency parameters. These are PLACEHOLDERS used\n// only to shape the dry-run fixture; they are NOT measurements and never leave a\n// dry-run row (Measurement stays \"dry-run-fixture\").\ntype benchModel struct {\n\tid string\n\tprefillMsPer float64 // fixture prefill ms per 1K prompt tokens\n\ttpotMs float64 // fixture per-output-token ms\n}\n\n// fixtureModels are V4 Pro and V4 Flash — both, always, side by side. The numbers\n// reflect only the coarse expectation that the 49B-active Pro decodes slower per\n// token than the 13B-active Flash; they are labelled placeholders, not evidence.\nfunc fixtureModels() []benchModel {\n\treturn []benchModel{\n\t\t{id: ModelV4Pro, prefillMsPer: 0.18, tpotMs: 22},\n\t\t{id: ModelV4Flash, prefillMsPer: 0.09, tpotMs: 9},\n\t}\n}\n\n// DryRunRows builds the deterministic no-key fixture: the full model × bucket ×\n// output × reasoning × stream matrix (skipping the contradictory non-thinking +\n// long-reasoning cells), plus one existing-fak-route baseline row so the scorecard\n// has a same-harness comparand to (correctly) REFUSE comparing against in dry-run.\nfunc DryRunRows() []Row {\n\tvar rows []Row\n\tfor _, m := range fixtureModels() {\n\t\tfor _, bucket := range ContextBuckets {\n\t\t\tfor _, output := range OutputTargets {\n\t\t\t\tfor _, reasoning := range ReasoningModes {\n\t\t\t\t\tif output == \"long-reasoning\" && reasoning == \"non-thinking\" {\n\t\t\t\t\t\tcontinue // a non-thinking route does not emit a long reasoning trace\n\t\t\t\t\t}\n\t\t\t\t\tfor _, stream := range []bool{true, false} {\n\t\t\t\t\t\trows = append(rows, fixtureRow(m, bucket, output, reasoning, stream))\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// One existing-fak-route baseline row, same harness, same schema. It is also a\n\t// fixture, so the scorecard MUST refuse to compare it (Measurement != live).\n\trows = append(rows, Row{\n\t\tMeasurement: \"dry-run-fixture\",\n\t\tSpeedProvenance: \"fixture-placeholder-not-measured\",\n\t\tModelID: \"claude-sonnet-5\",\n\t\tProviderRoute: \"anthropic\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: \"32K\",\n\t\tOutputTarget: \"1K\",\n\t\tReasoningMode: \"non-thinking\",\n\t\tStream: true,\n\t\tSpeculative: \"off\",\n\t\tCacheAttribution: \"unknown-dry-run\",\n\t\tAcceptedTokenRatio: \"unknown\",\n\t\tPromptShapeKey: PromptShape(\"32K\", \"1K\", \"non-thinking\", true),\n\t\tQualityParity: \"unknown\",\n\t\tPromptTokens: contextBucketTokens(\"32K\"),\n\t\tCompletionTokens: outputTargetTokens(\"1K\"),\n\t})\n\treturn rows\n}\n\n// fixtureRow computes one deterministic placeholder row. No time/rand: every number\n// is a pure function of the axes, so the fixture is byte-stable across runs.\nfunc fixtureRow(m benchModel, bucket, output, reasoning string, stream bool) Row {\n\tprompt := contextBucketTokens(bucket)\n\tcompletion := outputTargetTokens(output)\n\treasoningTokens := 0\n\tswitch reasoning {\n\tcase \"high\":\n\t\treasoningTokens = completion\n\tcase \"max\":\n\t\treasoningTokens = completion * 3\n\t}\n\tgenTokens := completion + reasoningTokens\n\tttft := (float64(prompt) / 1000.0) * m.prefillMsPer\n\ttpot := m.tpotMs\n\tvar e2e, toksPerSec float64\n\tif stream {\n\t\te2e = ttft + tpot*float64(genTokens)\n\t} else {\n\t\t// Non-stream: no incremental token stream, so TPOT is not observable; the whole\n\t\t// generation lands at once and TTFT collapses into E2E.\n\t\ttpot = 0\n\t\te2e = ttft + m.tpotMs*float64(genTokens)\n\t\tttft = e2e\n\t}\n\tif e2e > 0 {\n\t\ttoksPerSec = float64(genTokens) / (e2e / 1000.0)\n\t}\n\treturn Row{\n\t\tMeasurement: \"dry-run-fixture\",\n\t\tSpeedProvenance: \"fixture-placeholder-not-measured\",\n\t\tModelID: m.id,\n\t\tProviderRoute: \"deepseek\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: bucket,\n\t\tOutputTarget: output,\n\t\tReasoningMode: reasoning,\n\t\tStream: stream,\n\t\tSpeculative: \"off\",\n\t\tTTFTMillis: round2(ttft),\n\t\tTPOTMillis: round2(tpot),\n\t\tE2EMillis: round2(e2e),\n\t\tOutputToksPerSec: round2(toksPerSec),\n\t\tPromptTokens: prompt,\n\t\tCompletionTokens: completion,\n\t\tReasoningTokens: reasoningTokens,\n\t\t// The fixture asserts nothing about cache behavior — a real hit/miss split is a\n\t\t// provider-observed number only a live run may fill.\n\t\tPromptCacheHitTokens: 0,\n\t\tPromptCacheMissTokens: 0,\n\t\tCacheAttribution: \"unknown-dry-run\",\n\t\tAcceptedTokenRatio: \"unknown\",\n\t\tPromptShapeKey: PromptShape(bucket, output, reasoning, stream),\n\t\tQualityParity: \"unknown\",\n\t}\n}\n\nfunc round2(v float64) float64 {\n\treturn float64(int64(v*100+0.5)) / 100\n}\n\n// validSpeculative reports whether label is one of the locked SpeculativeModes.\nfunc validSpeculative(label string) bool {\n\tfor _, m := range SpeculativeModes {\n\t\tif label == m {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}\n\n// CompareSpeedup is the honesty gate the issue demands: it prints a speedup delta\n// ONLY when the DeepSeek row and the baseline row (a) share a prompt shape, (b) both\n// carry a verified quality parity, (c) are both live measurements, and (d) both carry\n// a valid speculative label from SpeculativeModes (#3020). Any other case returns a\n// \"[NOT COMPARABLE: …]\" line and printed=false — a dry-run fixture, a shape mismatch,\n// an unverified parity, or an unlabelled speculative mode can never surface as a\n// speed headline. When a delta IS printed, the subject's accepted_token_ratio rides\n// beside it, so a speculative speedup is never reported without its acceptance\n// evidence (or an honest \"unknown\").\nfunc CompareSpeedup(subject, baseline Row) (line string, printed bool) {\n\tswitch {\n\tcase subject.Measurement != \"live\" || baseline.Measurement != \"live\":\n\t\treturn \"[NOT COMPARABLE: dry-run fixture — no measured latency; run --live with a parity check]\", false\n\tcase subject.PromptShapeKey != baseline.PromptShapeKey:\n\t\treturn fmt.Sprintf(\"[NOT COMPARABLE: prompt shape differs (%s vs %s)]\", subject.PromptShapeKey, baseline.PromptShapeKey), false\n\tcase subject.QualityParity != \"verified\" || baseline.QualityParity != \"verified\":\n\t\treturn \"[NOT COMPARABLE: quality parity not verified for both rows]\", false\n\tcase !validSpeculative(subject.Speculative) || !validSpeculative(baseline.Speculative):\n\t\treturn \"[NOT COMPARABLE: missing speculative label]\", false\n\tcase baseline.E2EMillis <= 0 || subject.E2EMillis <= 0:\n\t\treturn \"[NOT COMPARABLE: a measured E2E is missing]\", false\n\t}\n\tratio := baseline.E2EMillis / subject.E2EMillis\n\treturn fmt.Sprintf(\"OBSERVED provider speed: %s is %.2f× the E2E of %s at shape %s (speculative=%s accepted_token_ratio=%s, provider-observed, not a fak-authored saving)\",\n\t\tsubject.ModelID, ratio, baseline.ModelID, subject.PromptShapeKey, subject.Speculative, subject.AcceptedTokenRatio), true\n}\n\n// LiveGate is the pure opt-in guard for a live run: it admits a live measurement\n// ONLY when a key is present AND spend was explicitly acknowledged, returning the\n// refusal message (and ok=false) otherwise. The wire consults this BEFORE any\n// network call, so default CI — no key, no --spend — never touches the network.\nfunc LiveGate(hasKey, spend bool) (msg string, ok bool) {\n\tif !hasKey {\n\t\treturn \"--live needs DEEPSEEK_API_KEY set (refusing before any network call)\", false\n\t}\n\tif !spend {\n\t\treturn \"--live costs money — pass --spend to acknowledge (refusing)\", false\n\t}\n\treturn \"\", true\n}\n\n// MeasureStreamed issues one streaming chat/completions request against an\n// OpenAI-compatible endpoint (hosted DeepSeek or a self-hosted vLLM/SGLang server)\n// and times TTFT (first content/reasoning delta), TPOT (mean inter-delta), E2E, and\n// reads the final usage block for the token + prompt-cache counters. The http client\n// is injectable so a canned httptest SSE server can witness the parsing/timing logic\n// with no key; production passes a real client. A live row — and only a live row —\n// carries Measurement=\"live\"/SpeedProvenance=\"provider-observed\".\nfunc MeasureStreamed(client *http.Client, baseURL, key, model string) (Row, error) {\n\tif client == nil {\n\t\tclient = &http.Client{Timeout: 5 * time.Minute}\n\t}\n\tbody, _ := json.Marshal(map[string]any{\n\t\t\"model\": model,\n\t\t\"stream\": true,\n\t\t\"messages\": []map[string]string{{\"role\": \"user\", \"content\": \"Reply with a short sentence.\"}},\n\t\t\"stream_options\": map[string]any{\"include_usage\": true},\n\t})\n\turl := strings.TrimRight(baseURL, \"/\") + \"/chat/completions\"\n\treq, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body))\n\tif err != nil {\n\t\treturn Row{}, err\n\t}\n\treq.Header.Set(\"Content-Type\", \"application/json\")\n\tif key != \"\" {\n\t\treq.Header.Set(\"Authorization\", \"Bearer \"+key)\n\t}\n\n\tstart := time.Now()\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn Row{}, err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode != http.StatusOK {\n\t\tsnippet, _ := io.ReadAll(io.LimitReader(resp.Body, 512))\n\t\treturn Row{}, fmt.Errorf(\"upstream status %d: %s\", resp.StatusCode, strings.TrimSpace(string(snippet)))\n\t}\n\n\tvar ttft time.Duration\n\tvar deltas int\n\tvar lastDelta time.Time\n\tvar interSum time.Duration\n\tprompt, completion, reasoning, hit, miss := 0, 0, 0, 0, 0\n\tsc := bufio.NewScanner(resp.Body)\n\tsc.Buffer(make([]byte, 0, 64*1024), 4*1024*1024)\n\tfor sc.Scan() {\n\t\tline := strings.TrimSpace(sc.Text())\n\t\tif !strings.HasPrefix(line, \"data:\") {\n\t\t\tcontinue\n\t\t}\n\t\tdata := strings.TrimSpace(strings.TrimPrefix(line, \"data:\"))\n\t\tif data == \"[DONE]\" {\n\t\t\tbreak\n\t\t}\n\t\tvar ev struct {\n\t\t\tChoices []struct {\n\t\t\t\tDelta struct {\n\t\t\t\t\tContent string `json:\"content\"`\n\t\t\t\t\tReasoningContent string `json:\"reasoning_content\"`\n\t\t\t\t} `json:\"delta\"`\n\t\t\t} `json:\"choices\"`\n\t\t\tUsage *struct {\n\t\t\t\tPromptTokens int `json:\"prompt_tokens\"`\n\t\t\t\tCompletionTokens int `json:\"completion_tokens\"`\n\t\t\t\tReasoningTokens int `json:\"reasoning_tokens\"`\n\t\t\t\tPromptCacheHitTokens int `json:\"prompt_cache_hit_tokens\"`\n\t\t\t\tPromptCacheMissTokens int `json:\"prompt_cache_miss_tokens\"`\n\t\t\t} `json:\"usage\"`\n\t\t}\n\t\tif err := json.Unmarshal([]byte(data), &ev); err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tif len(ev.Choices) > 0 && (ev.Choices[0].Delta.Content != \"\" || ev.Choices[0].Delta.ReasoningContent != \"\") {\n\t\t\tnow := time.Now()\n\t\t\tif ttft == 0 {\n\t\t\t\tttft = now.Sub(start)\n\t\t\t} else {\n\t\t\t\tinterSum += now.Sub(lastDelta)\n\t\t\t}\n\t\t\tlastDelta = now\n\t\t\tdeltas++\n\t\t}\n\t\tif ev.Usage != nil {\n\t\t\tprompt = ev.Usage.PromptTokens\n\t\t\tcompletion = ev.Usage.CompletionTokens\n\t\t\treasoning = ev.Usage.ReasoningTokens\n\t\t\thit = ev.Usage.PromptCacheHitTokens\n\t\t\tmiss = ev.Usage.PromptCacheMissTokens\n\t\t}\n\t}\n\tif err := sc.Err(); err != nil {\n\t\treturn Row{}, err\n\t}\n\te2e := time.Since(start)\n\ttpot := 0.0\n\tif deltas > 1 {\n\t\ttpot = float64(interSum.Milliseconds()) / float64(deltas-1)\n\t}\n\ttoksPerSec := 0.0\n\tif e2e > 0 && completion > 0 {\n\t\ttoksPerSec = float64(completion) / e2e.Seconds()\n\t}\n\tattribution := \"unknown\"\n\tif hit > 0 || miss > 0 {\n\t\tattribution = \"provider-observed\"\n\t}\n\treturn Row{\n\t\tMeasurement: \"live\",\n\t\tSpeedProvenance: \"provider-observed\",\n\t\tModelID: model,\n\t\tProviderRoute: \"deepseek\",\n\t\tEngineRoute: \"hosted-api\",\n\t\tHosting: \"hosted\",\n\t\tContextBucket: \"4K\",\n\t\tOutputTarget: \"short\",\n\t\tReasoningMode: \"non-thinking\",\n\t\tStream: true,\n\t\t// The plain streamed request does not enable a speculative decoder, and the\n\t\t// OpenAI-compatible usage block exposes no acceptance counter — so the row is\n\t\t// honestly labelled off/unknown rather than guessing.\n\t\tSpeculative: \"off\",\n\t\tAcceptedTokenRatio: \"unknown\",\n\t\tTTFTMillis: round2(float64(ttft.Microseconds()) / 1000.0),\n\t\tTPOTMillis: round2(tpot),\n\t\tE2EMillis: round2(float64(e2e.Microseconds()) / 1000.0),\n\t\tOutputToksPerSec: round2(toksPerSec),\n\t\tPromptTokens: prompt,\n\t\tCompletionTokens: completion,\n\t\tReasoningTokens: reasoning,\n\t\tPromptCacheHitTokens: hit,\n\t\tPromptCacheMissTokens: miss,\n\t\tCacheAttribution: attribution,\n\t\tPromptShapeKey: PromptShape(\"4K\", \"short\", \"non-thinking\", true),\n\t\t// A single request cannot assert quality parity against another route.\n\t\tQualityParity: \"unknown\",\n\t}, nil\n}\n"}}, "cmd/fak/route_accounts_test.go": {"f1c36fd45ecfb60dc33288bccf3477b30bbd53cf": {"t": 1785745633.1601665, "n": 11784, "h": "9fb4f064631b5367", "c": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// writeDumpedRoster dumps the built-in account roster to a temp file and returns its\n// path - the same starter `fak route --accounts-dump` emits for a user to edit.\nfunc writeDumpedRoster(t *testing.T) string {\n\tt.Helper()\n\tcode, out, _ := runRT(\"--accounts-dump\")\n\tif code != 0 || !strings.Contains(out, \"fak-accounts/v1\") {\n\t\tt.Fatalf(\"--accounts-dump failed: code=%d out=%s\", code, out)\n\t}\n\tpath := filepath.Join(t.TempDir(), \"roster.json\")\n\tif err := os.WriteFile(path, []byte(out), 0o600); err != nil {\n\t\tt.Fatalf(\"write roster: %v\", err)\n\t}\n\treturn path\n}\n\n// --accounts-dump emits a valid roster, and --accounts-check accepts it and prints the\n// account + binding surface.\nfunc TestRouteAccountsDumpAndCheck(t *testing.T) {\n\tpath := writeDumpedRoster(t)\n\tcode, out, _ := runRT(\"--accounts-check\", path)\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-check exit=%d out=%s\", code, out)\n\t}\n\tfor _, want := range []string{\"roster valid\", \"accounts:\", \"credential readiness\", \"bindings\", \"residency\"} {\n\t\tif !strings.Contains(out, want) {\n\t\t\tt.Fatalf(\"check surface missing %q:\\n%s\", want, out)\n\t\t}\n\t}\n}\n\nfunc TestRouteAccountsStatusReportsEnvReadiness(t *testing.T) {\n\tt.Setenv(\"OPENAI_API_KEY\", \"sk-must-not-print\")\n\tt.Setenv(\"OPENAI_WORK_API_KEY\", \"\")\n\tpath := writeDumpedRoster(t)\n\n\tcode, out, errs := runRT(\"--accounts-status\", path)\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-status exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tfor _, want := range []string{\"fak route accounts status\", \"needs_credential\", \"not_required\", \"OPENAI_WORK_API_KEY\"} {\n\t\tif !strings.Contains(out, want) {\n\t\t\tt.Fatalf(\"accounts status missing %q:\\n%s\", want, out)\n\t\t}\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"accounts status leaked a secret:\\n%s\", out)\n\t}\n\n\tcode, out, errs = runRT(\"--accounts-status\", path, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-status --json exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tSchema string `json:\"schema\"`\n\t\tSummary struct {\n\t\t\tTotal int `json:\"total\"`\n\t\t\tNeedsCredential int `json:\"needs_credential\"`\n\t\t} `json:\"summary\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif rep.Schema != \"fak.modelroute.accounts.v1\" || rep.Summary.Total == 0 || rep.Summary.NeedsCredential == 0 {\n\t\tt.Fatalf(\"unexpected readiness json: %+v\\n%s\", rep, out)\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"accounts status json leaked a secret:\\n%s\", out)\n\t}\n}\n\n// Routing a write-shaped tool call through the roster resolves BOTH guard-ensemble\n// members to their accounts - and they land on DIFFERENT accounts (the mix-and-match).\nfunc TestRouteAccountsBindsEnsembleAcrossAccounts(t *testing.T) {\n\tpath := writeDumpedRoster(t)\n\tcode, out, _ := runRT(\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d out=%s\", code, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 2 {\n\t\tt.Fatalf(\"want 2 bound members, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tif rep.Binding.Members[0].Account == rep.Binding.Members[1].Account {\n\t\tt.Fatalf(\"guard ensemble should span two accounts, both = %q\", rep.Binding.Members[0].Account)\n\t}\n\t// Each remote member's engine route is residency-honest (carries its kind, not local:).\n\tfor _, m := range rep.Binding.Members {\n\t\tif !m.Local && strings.HasPrefix(m.EngineRoute, \"local:\") {\n\t\t\tt.Fatalf(\"a remote member must not carry a local: route: %+v\", m)\n\t\t}\n\t}\n}\n\nfunc TestRouteAccountsBindsDeepSeekProfile(t *testing.T) {\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-must-not-print\")\n\troster := writeAPIHostDeepSeekModelAccountsRoster(t, \"https://api.deepseek.com\", \"https://api.deepseek.com/anthropic\")\n\tmanifest := filepath.Join(t.TempDir(), \"deepseek-route.json\")\n\tbody := `{\n \"version\": \"fak-route/v1\",\n \"default\": {\n \"members\": [{\"model\": \"deepseek-pro\", \"role\": \"primary\"}],\n \"reason\": \"DeepSeek V4 Pro profile witness\"\n }\n}`\n\tif err := os.WriteFile(manifest, []byte(body), 0o600); err != nil {\n\t\tt.Fatalf(\"write manifest: %v\", err)\n\t}\n\n\tcode, out, errs := runRT(\"--manifest\", manifest, \"--accounts\", roster, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tBaseURL string `json:\"base_url\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t\tUpstreamModel string `json:\"upstream_model\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tContextTokens int `json:\"context_tokens\"`\n\t\t\t\tMaxOutputTokens int `json:\"max_output_tokens\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 1 {\n\t\tt.Fatalf(\"want one bound DeepSeek member, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tm := rep.Binding.Members[0]\n\tif m.Account != \"deepseek\" || m.Kind != \"deepseek\" || m.BaseURL != \"https://api.deepseek.com\" ||\n\t\tm.CredEnv != \"DEEPSEEK_API_KEY\" || m.UpstreamModel != \"deepseek-v4-pro\" ||\n\t\tm.Local || m.EngineRoute != \"deepseek:deepseek/deepseek-v4-pro\" ||\n\t\tm.ContextTokens != 1000000 || m.MaxOutputTokens != 384000 {\n\t\tt.Fatalf(\"DeepSeek binding wrong: %+v\\n%s\", m, out)\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"route output leaked a secret:\\n%s\", out)\n\t}\n}\n\nfunc TestRouteAccountsBindsGroqQwen36Profile(t *testing.T) {\n\tt.Setenv(\"FAK_GROQ_API_KEY\", \"fake-groq-secret-value\")\n\troster := writeDumpedRoster(t)\n\tmanifest := filepath.Join(t.TempDir(), \"groq-route.json\")\n\tbody := `{\n \"version\": \"fak-route/v1\",\n \"default\": {\n \"members\": [{\"model\": \"qwen36-groq\", \"role\": \"primary\"}],\n \"reason\": \"Groq Qwen3.6 27B profile witness\"\n }\n}`\n\tif err := os.WriteFile(manifest, []byte(body), 0o600); err != nil {\n\t\tt.Fatalf(\"write manifest: %v\", err)\n\t}\n\n\tcode, out, errs := runRT(\"--manifest\", manifest, \"--accounts\", roster, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tBaseURL string `json:\"base_url\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t\tUpstreamModel string `json:\"upstream_model\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tRequestsPerMinute int `json:\"requests_per_minute\"`\n\t\t\t\tRequestsPerDay int `json:\"requests_per_day\"`\n\t\t\t\tTokensPerMinute int `json:\"tokens_per_minute\"`\n\t\t\t\tTokensPerDay int `json:\"tokens_per_day\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 1 {\n\t\tt.Fatalf(\"want one bound Groq member, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tm := rep.Binding.Members[0]\n\tif m.Account != \"july6netra_groq\" || m.Kind != \"openai\" || m.BaseURL != \"https://api.groq.com/openai/v1\" ||\n\t\tm.CredEnv != \"FAK_GROQ_API_KEY\" || m.UpstreamModel != \"qwen/qwen3.6-27b\" ||\n\t\tm.Local || m.EngineRoute != \"openai:july6netra_groq/qwen/qwen3.6-27b\" ||\n\t\tm.RequestsPerMinute != 30 || m.RequestsPerDay != 1000 ||\n\t\tm.TokensPerMinute != 8000 || m.TokensPerDay != 200000 {\n\t\tt.Fatalf(\"Groq Qwen3.6 binding wrong: %+v\\n%s\", m, out)\n\t}\n\tif strings.Contains(out, \"fake-groq-secret-value\") {\n\t\tt.Fatalf(\"route output leaked a Groq key value:\\n%s\", out)\n\t}\n}\n\nfunc TestRouteAccountsBindsGroqCompoundProfile(t *testing.T) {\n\tt.Setenv(\"FAK_GROQ_API_KEY\", \"fake-groq-secret-value\")\n\troster := writeDumpedRoster(t)\n\tmanifest := filepath.Join(t.TempDir(), \"groq-compound-route.json\")\n\tbody := `{\n \"version\": \"fak-route/v1\",\n \"default\": {\n \"members\": [{\"model\": \"groq-compound\", \"role\": \"primary\"}],\n \"reason\": \"Groq Compound lower-quality tier witness\"\n }\n}`\n\tif err := os.WriteFile(manifest, []byte(body), 0o600); err != nil {\n\t\tt.Fatalf(\"write manifest: %v\", err)\n\t}\n\n\tcode, out, errs := runRT(\"--manifest\", manifest, \"--accounts\", roster, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tBaseURL string `json:\"base_url\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t\tUpstreamModel string `json:\"upstream_model\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tRequestsPerMinute int `json:\"requests_per_minute\"`\n\t\t\t\tRequestsPerDay int `json:\"requests_per_day\"`\n\t\t\t\tTokensPerMinute int `json:\"tokens_per_minute\"`\n\t\t\t\tTokensPerDay int `json:\"tokens_per_day\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 1 {\n\t\tt.Fatalf(\"want one bound Groq Compound member, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tm := rep.Binding.Members[0]\n\tif m.Account != \"july6netra_groq_compound\" || m.Kind != \"openai\" || m.BaseURL != \"https://api.groq.com/openai/v1\" ||\n\t\tm.CredEnv != \"FAK_GROQ_API_KEY\" || m.UpstreamModel != \"groq/compound\" ||\n\t\tm.Local || m.EngineRoute != \"openai:july6netra_groq_compound/groq/compound\" ||\n\t\tm.RequestsPerMinute != 30 || m.RequestsPerDay != 250 ||\n\t\tm.TokensPerMinute != 0 || m.TokensPerDay != 0 {\n\t\tt.Fatalf(\"Groq Compound binding wrong: %+v\\n%s\", m, out)\n\t}\n\tif strings.Contains(out, \"fake-groq-secret-value\") {\n\t\tt.Fatalf(\"route output leaked a Groq key value:\\n%s\", out)\n\t}\n}\n\n// The CLI output (human and JSON) carries credential ENV-VAR NAMES, never secrets,\n// even when the env var holds a key.\nfunc TestRouteAccountsNeverPrintsSecret(t *testing.T) {\n\tt.Setenv(\"OPENAI_WORK_API_KEY\", \"sk-must-not-print\")\n\tpath := writeDumpedRoster(t)\n\tfor _, args := range [][]string{\n\t\t{\"--accounts-check\", path},\n\t\t{\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path},\n\t\t{\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path, \"--json\"},\n\t} {\n\t\t_, out, _ := runRT(args...)\n\t\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\t\tt.Fatalf(\"CLI leaked a secret for args %v:\\n%s\", args, out)\n\t\t}\n\t\tif !strings.Contains(out, \"OPENAI_WORK_API_KEY\") {\n\t\t\tt.Fatalf(\"CLI should show the env-var NAME for args %v:\\n%s\", args, out)\n\t\t}\n\t}\n}\n\n// A roster that fails validation (here, a local account with a remote base_url) is a\n// fail-loud error at --accounts-check, exit 1.\nfunc TestRouteAccountsCheckFailsLoud(t *testing.T) {\n\tbad := `{\"version\":\"fak-accounts/v1\",\"accounts\":[{\"id\":\"l\",\"kind\":\"local\",\"base_url\":\"https://api.openai.com/v1\"}]}`\n\tpath := filepath.Join(t.TempDir(), \"bad.json\")\n\tif err := os.WriteFile(path, []byte(bad), 0o600); err != nil {\n\t\tt.Fatalf(\"write: %v\", err)\n\t}\n\tcode, _, errs := runRT(\"--accounts-check\", path)\n\tif code != 1 {\n\t\tt.Fatalf(\"a residency-bypass roster must exit 1, got %d (stderr=%s)\", code, errs)\n\t}\n}\n"}, "a38d4bda14bc1acece8b90602413c114b57e5ca2": {"t": 1785749567.878482, "n": 11784, "h": "9fb4f064631b5367", "c": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// writeDumpedRoster dumps the built-in account roster to a temp file and returns its\n// path - the same starter `fak route --accounts-dump` emits for a user to edit.\nfunc writeDumpedRoster(t *testing.T) string {\n\tt.Helper()\n\tcode, out, _ := runRT(\"--accounts-dump\")\n\tif code != 0 || !strings.Contains(out, \"fak-accounts/v1\") {\n\t\tt.Fatalf(\"--accounts-dump failed: code=%d out=%s\", code, out)\n\t}\n\tpath := filepath.Join(t.TempDir(), \"roster.json\")\n\tif err := os.WriteFile(path, []byte(out), 0o600); err != nil {\n\t\tt.Fatalf(\"write roster: %v\", err)\n\t}\n\treturn path\n}\n\n// --accounts-dump emits a valid roster, and --accounts-check accepts it and prints the\n// account + binding surface.\nfunc TestRouteAccountsDumpAndCheck(t *testing.T) {\n\tpath := writeDumpedRoster(t)\n\tcode, out, _ := runRT(\"--accounts-check\", path)\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-check exit=%d out=%s\", code, out)\n\t}\n\tfor _, want := range []string{\"roster valid\", \"accounts:\", \"credential readiness\", \"bindings\", \"residency\"} {\n\t\tif !strings.Contains(out, want) {\n\t\t\tt.Fatalf(\"check surface missing %q:\\n%s\", want, out)\n\t\t}\n\t}\n}\n\nfunc TestRouteAccountsStatusReportsEnvReadiness(t *testing.T) {\n\tt.Setenv(\"OPENAI_API_KEY\", \"sk-must-not-print\")\n\tt.Setenv(\"OPENAI_WORK_API_KEY\", \"\")\n\tpath := writeDumpedRoster(t)\n\n\tcode, out, errs := runRT(\"--accounts-status\", path)\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-status exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tfor _, want := range []string{\"fak route accounts status\", \"needs_credential\", \"not_required\", \"OPENAI_WORK_API_KEY\"} {\n\t\tif !strings.Contains(out, want) {\n\t\t\tt.Fatalf(\"accounts status missing %q:\\n%s\", want, out)\n\t\t}\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"accounts status leaked a secret:\\n%s\", out)\n\t}\n\n\tcode, out, errs = runRT(\"--accounts-status\", path, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-status --json exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tSchema string `json:\"schema\"`\n\t\tSummary struct {\n\t\t\tTotal int `json:\"total\"`\n\t\t\tNeedsCredential int `json:\"needs_credential\"`\n\t\t} `json:\"summary\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif rep.Schema != \"fak.modelroute.accounts.v1\" || rep.Summary.Total == 0 || rep.Summary.NeedsCredential == 0 {\n\t\tt.Fatalf(\"unexpected readiness json: %+v\\n%s\", rep, out)\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"accounts status json leaked a secret:\\n%s\", out)\n\t}\n}\n\n// Routing a write-shaped tool call through the roster resolves BOTH guard-ensemble\n// members to their accounts - and they land on DIFFERENT accounts (the mix-and-match).\nfunc TestRouteAccountsBindsEnsembleAcrossAccounts(t *testing.T) {\n\tpath := writeDumpedRoster(t)\n\tcode, out, _ := runRT(\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d out=%s\", code, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 2 {\n\t\tt.Fatalf(\"want 2 bound members, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tif rep.Binding.Members[0].Account == rep.Binding.Members[1].Account {\n\t\tt.Fatalf(\"guard ensemble should span two accounts, both = %q\", rep.Binding.Members[0].Account)\n\t}\n\t// Each remote member's engine route is residency-honest (carries its kind, not local:).\n\tfor _, m := range rep.Binding.Members {\n\t\tif !m.Local && strings.HasPrefix(m.EngineRoute, \"local:\") {\n\t\t\tt.Fatalf(\"a remote member must not carry a local: route: %+v\", m)\n\t\t}\n\t}\n}\n\nfunc TestRouteAccountsBindsDeepSeekProfile(t *testing.T) {\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-must-not-print\")\n\troster := writeAPIHostDeepSeekModelAccountsRoster(t, \"https://api.deepseek.com\", \"https://api.deepseek.com/anthropic\")\n\tmanifest := filepath.Join(t.TempDir(), \"deepseek-route.json\")\n\tbody := `{\n \"version\": \"fak-route/v1\",\n \"default\": {\n \"members\": [{\"model\": \"deepseek-pro\", \"role\": \"primary\"}],\n \"reason\": \"DeepSeek V4 Pro profile witness\"\n }\n}`\n\tif err := os.WriteFile(manifest, []byte(body), 0o600); err != nil {\n\t\tt.Fatalf(\"write manifest: %v\", err)\n\t}\n\n\tcode, out, errs := runRT(\"--manifest\", manifest, \"--accounts\", roster, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tBaseURL string `json:\"base_url\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t\tUpstreamModel string `json:\"upstream_model\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tContextTokens int `json:\"context_tokens\"`\n\t\t\t\tMaxOutputTokens int `json:\"max_output_tokens\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 1 {\n\t\tt.Fatalf(\"want one bound DeepSeek member, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tm := rep.Binding.Members[0]\n\tif m.Account != \"deepseek\" || m.Kind != \"deepseek\" || m.BaseURL != \"https://api.deepseek.com\" ||\n\t\tm.CredEnv != \"DEEPSEEK_API_KEY\" || m.UpstreamModel != \"deepseek-v4-pro\" ||\n\t\tm.Local || m.EngineRoute != \"deepseek:deepseek/deepseek-v4-pro\" ||\n\t\tm.ContextTokens != 1000000 || m.MaxOutputTokens != 384000 {\n\t\tt.Fatalf(\"DeepSeek binding wrong: %+v\\n%s\", m, out)\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"route output leaked a secret:\\n%s\", out)\n\t}\n}\n\nfunc TestRouteAccountsBindsGroqQwen36Profile(t *testing.T) {\n\tt.Setenv(\"FAK_GROQ_API_KEY\", \"fake-groq-secret-value\")\n\troster := writeDumpedRoster(t)\n\tmanifest := filepath.Join(t.TempDir(), \"groq-route.json\")\n\tbody := `{\n \"version\": \"fak-route/v1\",\n \"default\": {\n \"members\": [{\"model\": \"qwen36-groq\", \"role\": \"primary\"}],\n \"reason\": \"Groq Qwen3.6 27B profile witness\"\n }\n}`\n\tif err := os.WriteFile(manifest, []byte(body), 0o600); err != nil {\n\t\tt.Fatalf(\"write manifest: %v\", err)\n\t}\n\n\tcode, out, errs := runRT(\"--manifest\", manifest, \"--accounts\", roster, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tBaseURL string `json:\"base_url\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t\tUpstreamModel string `json:\"upstream_model\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tRequestsPerMinute int `json:\"requests_per_minute\"`\n\t\t\t\tRequestsPerDay int `json:\"requests_per_day\"`\n\t\t\t\tTokensPerMinute int `json:\"tokens_per_minute\"`\n\t\t\t\tTokensPerDay int `json:\"tokens_per_day\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 1 {\n\t\tt.Fatalf(\"want one bound Groq member, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tm := rep.Binding.Members[0]\n\tif m.Account != \"july6netra_groq\" || m.Kind != \"openai\" || m.BaseURL != \"https://api.groq.com/openai/v1\" ||\n\t\tm.CredEnv != \"FAK_GROQ_API_KEY\" || m.UpstreamModel != \"qwen/qwen3.6-27b\" ||\n\t\tm.Local || m.EngineRoute != \"openai:july6netra_groq/qwen/qwen3.6-27b\" ||\n\t\tm.RequestsPerMinute != 30 || m.RequestsPerDay != 1000 ||\n\t\tm.TokensPerMinute != 8000 || m.TokensPerDay != 200000 {\n\t\tt.Fatalf(\"Groq Qwen3.6 binding wrong: %+v\\n%s\", m, out)\n\t}\n\tif strings.Contains(out, \"fake-groq-secret-value\") {\n\t\tt.Fatalf(\"route output leaked a Groq key value:\\n%s\", out)\n\t}\n}\n\nfunc TestRouteAccountsBindsGroqCompoundProfile(t *testing.T) {\n\tt.Setenv(\"FAK_GROQ_API_KEY\", \"fake-groq-secret-value\")\n\troster := writeDumpedRoster(t)\n\tmanifest := filepath.Join(t.TempDir(), \"groq-compound-route.json\")\n\tbody := `{\n \"version\": \"fak-route/v1\",\n \"default\": {\n \"members\": [{\"model\": \"groq-compound\", \"role\": \"primary\"}],\n \"reason\": \"Groq Compound lower-quality tier witness\"\n }\n}`\n\tif err := os.WriteFile(manifest, []byte(body), 0o600); err != nil {\n\t\tt.Fatalf(\"write manifest: %v\", err)\n\t}\n\n\tcode, out, errs := runRT(\"--manifest\", manifest, \"--accounts\", roster, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tBaseURL string `json:\"base_url\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t\tUpstreamModel string `json:\"upstream_model\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tRequestsPerMinute int `json:\"requests_per_minute\"`\n\t\t\t\tRequestsPerDay int `json:\"requests_per_day\"`\n\t\t\t\tTokensPerMinute int `json:\"tokens_per_minute\"`\n\t\t\t\tTokensPerDay int `json:\"tokens_per_day\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 1 {\n\t\tt.Fatalf(\"want one bound Groq Compound member, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tm := rep.Binding.Members[0]\n\tif m.Account != \"july6netra_groq_compound\" || m.Kind != \"openai\" || m.BaseURL != \"https://api.groq.com/openai/v1\" ||\n\t\tm.CredEnv != \"FAK_GROQ_API_KEY\" || m.UpstreamModel != \"groq/compound\" ||\n\t\tm.Local || m.EngineRoute != \"openai:july6netra_groq_compound/groq/compound\" ||\n\t\tm.RequestsPerMinute != 30 || m.RequestsPerDay != 250 ||\n\t\tm.TokensPerMinute != 0 || m.TokensPerDay != 0 {\n\t\tt.Fatalf(\"Groq Compound binding wrong: %+v\\n%s\", m, out)\n\t}\n\tif strings.Contains(out, \"fake-groq-secret-value\") {\n\t\tt.Fatalf(\"route output leaked a Groq key value:\\n%s\", out)\n\t}\n}\n\n// The CLI output (human and JSON) carries credential ENV-VAR NAMES, never secrets,\n// even when the env var holds a key.\nfunc TestRouteAccountsNeverPrintsSecret(t *testing.T) {\n\tt.Setenv(\"OPENAI_WORK_API_KEY\", \"sk-must-not-print\")\n\tpath := writeDumpedRoster(t)\n\tfor _, args := range [][]string{\n\t\t{\"--accounts-check\", path},\n\t\t{\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path},\n\t\t{\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path, \"--json\"},\n\t} {\n\t\t_, out, _ := runRT(args...)\n\t\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\t\tt.Fatalf(\"CLI leaked a secret for args %v:\\n%s\", args, out)\n\t\t}\n\t\tif !strings.Contains(out, \"OPENAI_WORK_API_KEY\") {\n\t\t\tt.Fatalf(\"CLI should show the env-var NAME for args %v:\\n%s\", args, out)\n\t\t}\n\t}\n}\n\n// A roster that fails validation (here, a local account with a remote base_url) is a\n// fail-loud error at --accounts-check, exit 1.\nfunc TestRouteAccountsCheckFailsLoud(t *testing.T) {\n\tbad := `{\"version\":\"fak-accounts/v1\",\"accounts\":[{\"id\":\"l\",\"kind\":\"local\",\"base_url\":\"https://api.openai.com/v1\"}]}`\n\tpath := filepath.Join(t.TempDir(), \"bad.json\")\n\tif err := os.WriteFile(path, []byte(bad), 0o600); err != nil {\n\t\tt.Fatalf(\"write: %v\", err)\n\t}\n\tcode, _, errs := runRT(\"--accounts-check\", path)\n\tif code != 1 {\n\t\tt.Fatalf(\"a residency-bypass roster must exit 1, got %d (stderr=%s)\", code, errs)\n\t}\n}\n"}, "e6f5e92d9da484b41d18307659a2008b7b74f4a6": {"t": 1785749569.0613017, "n": 7420, "h": "065b8323e468ca4d", "c": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// writeDumpedRoster dumps the built-in account roster to a temp file and returns its\n// path - the same starter `fak route --accounts-dump` emits for a user to edit.\nfunc writeDumpedRoster(t *testing.T) string {\n\tt.Helper()\n\tcode, out, _ := runRT(\"--accounts-dump\")\n\tif code != 0 || !strings.Contains(out, \"fak-accounts/v1\") {\n\t\tt.Fatalf(\"--accounts-dump failed: code=%d out=%s\", code, out)\n\t}\n\tpath := filepath.Join(t.TempDir(), \"roster.json\")\n\tif err := os.WriteFile(path, []byte(out), 0o600); err != nil {\n\t\tt.Fatalf(\"write roster: %v\", err)\n\t}\n\treturn path\n}\n\n// --accounts-dump emits a valid roster, and --accounts-check accepts it and prints the\n// account + binding surface.\nfunc TestRouteAccountsDumpAndCheck(t *testing.T) {\n\tpath := writeDumpedRoster(t)\n\tcode, out, _ := runRT(\"--accounts-check\", path)\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-check exit=%d out=%s\", code, out)\n\t}\n\tfor _, want := range []string{\"roster valid\", \"accounts:\", \"credential readiness\", \"bindings\", \"residency\"} {\n\t\tif !strings.Contains(out, want) {\n\t\t\tt.Fatalf(\"check surface missing %q:\\n%s\", want, out)\n\t\t}\n\t}\n}\n\nfunc TestRouteAccountsStatusReportsEnvReadiness(t *testing.T) {\n\tt.Setenv(\"OPENAI_API_KEY\", \"sk-must-not-print\")\n\tt.Setenv(\"OPENAI_WORK_API_KEY\", \"\")\n\tpath := writeDumpedRoster(t)\n\n\tcode, out, errs := runRT(\"--accounts-status\", path)\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-status exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tfor _, want := range []string{\"fak route accounts status\", \"needs_credential\", \"not_required\", \"OPENAI_WORK_API_KEY\"} {\n\t\tif !strings.Contains(out, want) {\n\t\t\tt.Fatalf(\"accounts status missing %q:\\n%s\", want, out)\n\t\t}\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"accounts status leaked a secret:\\n%s\", out)\n\t}\n\n\tcode, out, errs = runRT(\"--accounts-status\", path, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-status --json exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tSchema string `json:\"schema\"`\n\t\tSummary struct {\n\t\t\tTotal int `json:\"total\"`\n\t\t\tNeedsCredential int `json:\"needs_credential\"`\n\t\t} `json:\"summary\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif rep.Schema != \"fak.modelroute.accounts.v1\" || rep.Summary.Total == 0 || rep.Summary.NeedsCredential == 0 {\n\t\tt.Fatalf(\"unexpected readiness json: %+v\\n%s\", rep, out)\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"accounts status json leaked a secret:\\n%s\", out)\n\t}\n}\n\n// Routing a write-shaped tool call through the roster resolves BOTH guard-ensemble\n// members to their accounts - and they land on DIFFERENT accounts (the mix-and-match).\nfunc TestRouteAccountsBindsEnsembleAcrossAccounts(t *testing.T) {\n\tpath := writeDumpedRoster(t)\n\tcode, out, _ := runRT(\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d out=%s\", code, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 2 {\n\t\tt.Fatalf(\"want 2 bound members, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tif rep.Binding.Members[0].Account == rep.Binding.Members[1].Account {\n\t\tt.Fatalf(\"guard ensemble should span two accounts, both = %q\", rep.Binding.Members[0].Account)\n\t}\n\t// Each remote member's engine route is residency-honest (carries its kind, not local:).\n\tfor _, m := range rep.Binding.Members {\n\t\tif !m.Local && strings.HasPrefix(m.EngineRoute, \"local:\") {\n\t\t\tt.Fatalf(\"a remote member must not carry a local: route: %+v\", m)\n\t\t}\n\t}\n}\n\nfunc TestRouteAccountsBindsDeepSeekProfile(t *testing.T) {\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-must-not-print\")\n\troster := writeAPIHostDeepSeekModelAccountsRoster(t, \"https://api.deepseek.com\", \"https://api.deepseek.com/anthropic\")\n\tmanifest := filepath.Join(t.TempDir(), \"deepseek-route.json\")\n\tbody := `{\n \"version\": \"fak-route/v1\",\n \"default\": {\n \"members\": [{\"model\": \"deepseek-pro\", \"role\": \"primary\"}],\n \"reason\": \"DeepSeek V4 Pro profile witness\"\n }\n}`\n\tif err := os.WriteFile(manifest, []byte(body), 0o600); err != nil {\n\t\tt.Fatalf(\"write manifest: %v\", err)\n\t}\n\n\tcode, out, errs := runRT(\"--manifest\", manifest, \"--accounts\", roster, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tBaseURL string `json:\"base_url\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t\tUpstreamModel string `json:\"upstream_model\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tContextTokens int `json:\"context_tokens\"`\n\t\t\t\tMaxOutputTokens int `json:\"max_output_tokens\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 1 {\n\t\tt.Fatalf(\"want one bound DeepSeek member, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tm := rep.Binding.Members[0]\n\tif m.Account != \"deepseek\" || m.Kind != \"deepseek\" || m.BaseURL != \"https://api.deepseek.com\" ||\n\t\tm.CredEnv != \"DEEPSEEK_API_KEY\" || m.UpstreamModel != \"deepseek-v4-pro\" ||\n\t\tm.Local || m.EngineRoute != \"deepseek:deepseek/deepseek-v4-pro\" ||\n\t\tm.ContextTokens != 1000000 || m.MaxOutputTokens != 384000 {\n\t\tt.Fatalf(\"DeepSeek binding wrong: %+v\\n%s\", m, out)\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"route output leaked a secret:\\n%s\", out)\n\t}\n}\n\n// The CLI output (human and JSON) carries credential ENV-VAR NAMES, never secrets,\n// even when the env var holds a key.\nfunc TestRouteAccountsNeverPrintsSecret(t *testing.T) {\n\tt.Setenv(\"OPENAI_WORK_API_KEY\", \"sk-must-not-print\")\n\tpath := writeDumpedRoster(t)\n\tfor _, args := range [][]string{\n\t\t{\"--accounts-check\", path},\n\t\t{\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path},\n\t\t{\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path, \"--json\"},\n\t} {\n\t\t_, out, _ := runRT(args...)\n\t\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\t\tt.Fatalf(\"CLI leaked a secret for args %v:\\n%s\", args, out)\n\t\t}\n\t\tif !strings.Contains(out, \"OPENAI_WORK_API_KEY\") {\n\t\t\tt.Fatalf(\"CLI should show the env-var NAME for args %v:\\n%s\", args, out)\n\t\t}\n\t}\n}\n\n// A roster that fails validation (here, a local account with a remote base_url) is a\n// fail-loud error at --accounts-check, exit 1.\nfunc TestRouteAccountsCheckFailsLoud(t *testing.T) {\n\tbad := `{\"version\":\"fak-accounts/v1\",\"accounts\":[{\"id\":\"l\",\"kind\":\"local\",\"base_url\":\"https://api.openai.com/v1\"}]}`\n\tpath := filepath.Join(t.TempDir(), \"bad.json\")\n\tif err := os.WriteFile(path, []byte(bad), 0o600); err != nil {\n\t\tt.Fatalf(\"write: %v\", err)\n\t}\n\tcode, _, errs := runRT(\"--accounts-check\", path)\n\tif code != 1 {\n\t\tt.Fatalf(\"a residency-bypass roster must exit 1, got %d (stderr=%s)\", code, errs)\n\t}\n}\n"}, "3df092abc6800848f097dc6cf87669ffe0afd666": {"t": 1785749570.2541552, "n": 5389, "h": "e27a4901008da1a2", "c": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// writeDumpedRoster dumps the built-in account roster to a temp file and returns its\n// path - the same starter `fak route --accounts-dump` emits for a user to edit.\nfunc writeDumpedRoster(t *testing.T) string {\n\tt.Helper()\n\tcode, out, _ := runRT(\"--accounts-dump\")\n\tif code != 0 || !strings.Contains(out, \"fak-accounts/v1\") {\n\t\tt.Fatalf(\"--accounts-dump failed: code=%d out=%s\", code, out)\n\t}\n\tpath := filepath.Join(t.TempDir(), \"roster.json\")\n\tif err := os.WriteFile(path, []byte(out), 0o600); err != nil {\n\t\tt.Fatalf(\"write roster: %v\", err)\n\t}\n\treturn path\n}\n\n// --accounts-dump emits a valid roster, and --accounts-check accepts it and prints the\n// account + binding surface.\nfunc TestRouteAccountsDumpAndCheck(t *testing.T) {\n\tpath := writeDumpedRoster(t)\n\tcode, out, _ := runRT(\"--accounts-check\", path)\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-check exit=%d out=%s\", code, out)\n\t}\n\tfor _, want := range []string{\"roster valid\", \"accounts:\", \"credential readiness\", \"bindings\", \"residency\"} {\n\t\tif !strings.Contains(out, want) {\n\t\t\tt.Fatalf(\"check surface missing %q:\\n%s\", want, out)\n\t\t}\n\t}\n}\n\nfunc TestRouteAccountsStatusReportsEnvReadiness(t *testing.T) {\n\tt.Setenv(\"OPENAI_API_KEY\", \"sk-must-not-print\")\n\tt.Setenv(\"OPENAI_WORK_API_KEY\", \"\")\n\tpath := writeDumpedRoster(t)\n\n\tcode, out, errs := runRT(\"--accounts-status\", path)\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-status exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tfor _, want := range []string{\"fak route accounts status\", \"needs_credential\", \"not_required\", \"OPENAI_WORK_API_KEY\"} {\n\t\tif !strings.Contains(out, want) {\n\t\t\tt.Fatalf(\"accounts status missing %q:\\n%s\", want, out)\n\t\t}\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"accounts status leaked a secret:\\n%s\", out)\n\t}\n\n\tcode, out, errs = runRT(\"--accounts-status\", path, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-status --json exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tSchema string `json:\"schema\"`\n\t\tSummary struct {\n\t\t\tTotal int `json:\"total\"`\n\t\t\tNeedsCredential int `json:\"needs_credential\"`\n\t\t} `json:\"summary\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif rep.Schema != \"fak.modelroute.accounts.v1\" || rep.Summary.Total == 0 || rep.Summary.NeedsCredential == 0 {\n\t\tt.Fatalf(\"unexpected readiness json: %+v\\n%s\", rep, out)\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"accounts status json leaked a secret:\\n%s\", out)\n\t}\n}\n\n// Routing a write-shaped tool call through the roster resolves BOTH guard-ensemble\n// members to their accounts - and they land on DIFFERENT accounts (the mix-and-match).\nfunc TestRouteAccountsBindsEnsembleAcrossAccounts(t *testing.T) {\n\tpath := writeDumpedRoster(t)\n\tcode, out, _ := runRT(\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d out=%s\", code, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 2 {\n\t\tt.Fatalf(\"want 2 bound members, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tif rep.Binding.Members[0].Account == rep.Binding.Members[1].Account {\n\t\tt.Fatalf(\"guard ensemble should span two accounts, both = %q\", rep.Binding.Members[0].Account)\n\t}\n\t// Each remote member's engine route is residency-honest (carries its kind, not local:).\n\tfor _, m := range rep.Binding.Members {\n\t\tif !m.Local && strings.HasPrefix(m.EngineRoute, \"local:\") {\n\t\t\tt.Fatalf(\"a remote member must not carry a local: route: %+v\", m)\n\t\t}\n\t}\n}\n\n// The CLI output (human and JSON) carries credential ENV-VAR NAMES, never secrets,\n// even when the env var holds a key.\nfunc TestRouteAccountsNeverPrintsSecret(t *testing.T) {\n\tt.Setenv(\"OPENAI_WORK_API_KEY\", \"sk-must-not-print\")\n\tpath := writeDumpedRoster(t)\n\tfor _, args := range [][]string{\n\t\t{\"--accounts-check\", path},\n\t\t{\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path},\n\t\t{\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path, \"--json\"},\n\t} {\n\t\t_, out, _ := runRT(args...)\n\t\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\t\tt.Fatalf(\"CLI leaked a secret for args %v:\\n%s\", args, out)\n\t\t}\n\t\tif !strings.Contains(out, \"OPENAI_WORK_API_KEY\") {\n\t\t\tt.Fatalf(\"CLI should show the env-var NAME for args %v:\\n%s\", args, out)\n\t\t}\n\t}\n}\n\n// A roster that fails validation (here, a local account with a remote base_url) is a\n// fail-loud error at --accounts-check, exit 1.\nfunc TestRouteAccountsCheckFailsLoud(t *testing.T) {\n\tbad := `{\"version\":\"fak-accounts/v1\",\"accounts\":[{\"id\":\"l\",\"kind\":\"local\",\"base_url\":\"https://api.openai.com/v1\"}]}`\n\tpath := filepath.Join(t.TempDir(), \"bad.json\")\n\tif err := os.WriteFile(path, []byte(bad), 0o600); err != nil {\n\t\tt.Fatalf(\"write: %v\", err)\n\t}\n\tcode, _, errs := runRT(\"--accounts-check\", path)\n\tif code != 1 {\n\t\tt.Fatalf(\"a residency-bypass roster must exit 1, got %d (stderr=%s)\", code, errs)\n\t}\n}\n"}, "2e007276ba5c858babc85bcdff9eb19d8629cc88": {"t": 1785749571.3342972, "n": 3976, "h": "9fee433f7227fa01", "c": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// writeDumpedRoster dumps the built-in account roster to a temp file and returns its\n// path - the same starter `fak route --accounts-dump` emits for a user to edit.\nfunc writeDumpedRoster(t *testing.T) string {\n\tt.Helper()\n\tcode, out, _ := runRT(\"--accounts-dump\")\n\tif code != 0 || !strings.Contains(out, \"fak-accounts/v1\") {\n\t\tt.Fatalf(\"--accounts-dump failed: code=%d out=%s\", code, out)\n\t}\n\tpath := filepath.Join(t.TempDir(), \"roster.json\")\n\tif err := os.WriteFile(path, []byte(out), 0o600); err != nil {\n\t\tt.Fatalf(\"write roster: %v\", err)\n\t}\n\treturn path\n}\n\n// --accounts-dump emits a valid roster, and --accounts-check accepts it and prints the\n// account + binding surface.\nfunc TestRouteAccountsDumpAndCheck(t *testing.T) {\n\tpath := writeDumpedRoster(t)\n\tcode, out, _ := runRT(\"--accounts-check\", path)\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-check exit=%d out=%s\", code, out)\n\t}\n\tfor _, want := range []string{\"roster valid\", \"accounts:\", \"bindings\", \"residency\"} {\n\t\tif !strings.Contains(out, want) {\n\t\t\tt.Fatalf(\"check surface missing %q:\\n%s\", want, out)\n\t\t}\n\t}\n}\n\n// Routing a write-shaped tool call through the roster resolves BOTH guard-ensemble\n// members to their accounts - and they land on DIFFERENT accounts (the mix-and-match).\nfunc TestRouteAccountsBindsEnsembleAcrossAccounts(t *testing.T) {\n\tpath := writeDumpedRoster(t)\n\tcode, out, _ := runRT(\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d out=%s\", code, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 2 {\n\t\tt.Fatalf(\"want 2 bound members, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tif rep.Binding.Members[0].Account == rep.Binding.Members[1].Account {\n\t\tt.Fatalf(\"guard ensemble should span two accounts, both = %q\", rep.Binding.Members[0].Account)\n\t}\n\t// Each remote member's engine route is residency-honest (carries its kind, not local:).\n\tfor _, m := range rep.Binding.Members {\n\t\tif !m.Local && strings.HasPrefix(m.EngineRoute, \"local:\") {\n\t\t\tt.Fatalf(\"a remote member must not carry a local: route: %+v\", m)\n\t\t}\n\t}\n}\n\n// The CLI output (human and JSON) carries credential ENV-VAR NAMES, never secrets,\n// even when the env var holds a key.\nfunc TestRouteAccountsNeverPrintsSecret(t *testing.T) {\n\tt.Setenv(\"OPENAI_WORK_API_KEY\", \"sk-must-not-print\")\n\tpath := writeDumpedRoster(t)\n\tfor _, args := range [][]string{\n\t\t{\"--accounts-check\", path},\n\t\t{\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path},\n\t\t{\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path, \"--json\"},\n\t} {\n\t\t_, out, _ := runRT(args...)\n\t\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\t\tt.Fatalf(\"CLI leaked a secret for args %v:\\n%s\", args, out)\n\t\t}\n\t\tif !strings.Contains(out, \"OPENAI_WORK_API_KEY\") {\n\t\t\tt.Fatalf(\"CLI should show the env-var NAME for args %v:\\n%s\", args, out)\n\t\t}\n\t}\n}\n\n// A roster that fails validation (here, a local account with a remote base_url) is a\n// fail-loud error at --accounts-check, exit 1.\nfunc TestRouteAccountsCheckFailsLoud(t *testing.T) {\n\tbad := `{\"version\":\"fak-accounts/v1\",\"accounts\":[{\"id\":\"l\",\"kind\":\"local\",\"base_url\":\"https://api.openai.com/v1\"}]}`\n\tpath := filepath.Join(t.TempDir(), \"bad.json\")\n\tif err := os.WriteFile(path, []byte(bad), 0o600); err != nil {\n\t\tt.Fatalf(\"write: %v\", err)\n\t}\n\tcode, _, errs := runRT(\"--accounts-check\", path)\n\tif code != 1 {\n\t\tt.Fatalf(\"a residency-bypass roster must exit 1, got %d (stderr=%s)\", code, errs)\n\t}\n}\n"}, "13f5f1a6e0391eb28bcbea759890fb009251e5d1": {"t": 1786067422.4689784, "n": 11784, "h": "9fb4f064631b5367", "c": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// writeDumpedRoster dumps the built-in account roster to a temp file and returns its\n// path - the same starter `fak route --accounts-dump` emits for a user to edit.\nfunc writeDumpedRoster(t *testing.T) string {\n\tt.Helper()\n\tcode, out, _ := runRT(\"--accounts-dump\")\n\tif code != 0 || !strings.Contains(out, \"fak-accounts/v1\") {\n\t\tt.Fatalf(\"--accounts-dump failed: code=%d out=%s\", code, out)\n\t}\n\tpath := filepath.Join(t.TempDir(), \"roster.json\")\n\tif err := os.WriteFile(path, []byte(out), 0o600); err != nil {\n\t\tt.Fatalf(\"write roster: %v\", err)\n\t}\n\treturn path\n}\n\n// --accounts-dump emits a valid roster, and --accounts-check accepts it and prints the\n// account + binding surface.\nfunc TestRouteAccountsDumpAndCheck(t *testing.T) {\n\tpath := writeDumpedRoster(t)\n\tcode, out, _ := runRT(\"--accounts-check\", path)\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-check exit=%d out=%s\", code, out)\n\t}\n\tfor _, want := range []string{\"roster valid\", \"accounts:\", \"credential readiness\", \"bindings\", \"residency\"} {\n\t\tif !strings.Contains(out, want) {\n\t\t\tt.Fatalf(\"check surface missing %q:\\n%s\", want, out)\n\t\t}\n\t}\n}\n\nfunc TestRouteAccountsStatusReportsEnvReadiness(t *testing.T) {\n\tt.Setenv(\"OPENAI_API_KEY\", \"sk-must-not-print\")\n\tt.Setenv(\"OPENAI_WORK_API_KEY\", \"\")\n\tpath := writeDumpedRoster(t)\n\n\tcode, out, errs := runRT(\"--accounts-status\", path)\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-status exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tfor _, want := range []string{\"fak route accounts status\", \"needs_credential\", \"not_required\", \"OPENAI_WORK_API_KEY\"} {\n\t\tif !strings.Contains(out, want) {\n\t\t\tt.Fatalf(\"accounts status missing %q:\\n%s\", want, out)\n\t\t}\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"accounts status leaked a secret:\\n%s\", out)\n\t}\n\n\tcode, out, errs = runRT(\"--accounts-status\", path, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-status --json exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tSchema string `json:\"schema\"`\n\t\tSummary struct {\n\t\t\tTotal int `json:\"total\"`\n\t\t\tNeedsCredential int `json:\"needs_credential\"`\n\t\t} `json:\"summary\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif rep.Schema != \"fak.modelroute.accounts.v1\" || rep.Summary.Total == 0 || rep.Summary.NeedsCredential == 0 {\n\t\tt.Fatalf(\"unexpected readiness json: %+v\\n%s\", rep, out)\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"accounts status json leaked a secret:\\n%s\", out)\n\t}\n}\n\n// Routing a write-shaped tool call through the roster resolves BOTH guard-ensemble\n// members to their accounts - and they land on DIFFERENT accounts (the mix-and-match).\nfunc TestRouteAccountsBindsEnsembleAcrossAccounts(t *testing.T) {\n\tpath := writeDumpedRoster(t)\n\tcode, out, _ := runRT(\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d out=%s\", code, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 2 {\n\t\tt.Fatalf(\"want 2 bound members, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tif rep.Binding.Members[0].Account == rep.Binding.Members[1].Account {\n\t\tt.Fatalf(\"guard ensemble should span two accounts, both = %q\", rep.Binding.Members[0].Account)\n\t}\n\t// Each remote member's engine route is residency-honest (carries its kind, not local:).\n\tfor _, m := range rep.Binding.Members {\n\t\tif !m.Local && strings.HasPrefix(m.EngineRoute, \"local:\") {\n\t\t\tt.Fatalf(\"a remote member must not carry a local: route: %+v\", m)\n\t\t}\n\t}\n}\n\nfunc TestRouteAccountsBindsDeepSeekProfile(t *testing.T) {\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-must-not-print\")\n\troster := writeAPIHostDeepSeekModelAccountsRoster(t, \"https://api.deepseek.com\", \"https://api.deepseek.com/anthropic\")\n\tmanifest := filepath.Join(t.TempDir(), \"deepseek-route.json\")\n\tbody := `{\n \"version\": \"fak-route/v1\",\n \"default\": {\n \"members\": [{\"model\": \"deepseek-pro\", \"role\": \"primary\"}],\n \"reason\": \"DeepSeek V4 Pro profile witness\"\n }\n}`\n\tif err := os.WriteFile(manifest, []byte(body), 0o600); err != nil {\n\t\tt.Fatalf(\"write manifest: %v\", err)\n\t}\n\n\tcode, out, errs := runRT(\"--manifest\", manifest, \"--accounts\", roster, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tBaseURL string `json:\"base_url\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t\tUpstreamModel string `json:\"upstream_model\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tContextTokens int `json:\"context_tokens\"`\n\t\t\t\tMaxOutputTokens int `json:\"max_output_tokens\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 1 {\n\t\tt.Fatalf(\"want one bound DeepSeek member, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tm := rep.Binding.Members[0]\n\tif m.Account != \"deepseek\" || m.Kind != \"deepseek\" || m.BaseURL != \"https://api.deepseek.com\" ||\n\t\tm.CredEnv != \"DEEPSEEK_API_KEY\" || m.UpstreamModel != \"deepseek-v4-pro\" ||\n\t\tm.Local || m.EngineRoute != \"deepseek:deepseek/deepseek-v4-pro\" ||\n\t\tm.ContextTokens != 1000000 || m.MaxOutputTokens != 384000 {\n\t\tt.Fatalf(\"DeepSeek binding wrong: %+v\\n%s\", m, out)\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"route output leaked a secret:\\n%s\", out)\n\t}\n}\n\nfunc TestRouteAccountsBindsGroqQwen36Profile(t *testing.T) {\n\tt.Setenv(\"FAK_GROQ_API_KEY\", \"fake-groq-secret-value\")\n\troster := writeDumpedRoster(t)\n\tmanifest := filepath.Join(t.TempDir(), \"groq-route.json\")\n\tbody := `{\n \"version\": \"fak-route/v1\",\n \"default\": {\n \"members\": [{\"model\": \"qwen36-groq\", \"role\": \"primary\"}],\n \"reason\": \"Groq Qwen3.6 27B profile witness\"\n }\n}`\n\tif err := os.WriteFile(manifest, []byte(body), 0o600); err != nil {\n\t\tt.Fatalf(\"write manifest: %v\", err)\n\t}\n\n\tcode, out, errs := runRT(\"--manifest\", manifest, \"--accounts\", roster, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tBaseURL string `json:\"base_url\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t\tUpstreamModel string `json:\"upstream_model\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tRequestsPerMinute int `json:\"requests_per_minute\"`\n\t\t\t\tRequestsPerDay int `json:\"requests_per_day\"`\n\t\t\t\tTokensPerMinute int `json:\"tokens_per_minute\"`\n\t\t\t\tTokensPerDay int `json:\"tokens_per_day\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 1 {\n\t\tt.Fatalf(\"want one bound Groq member, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tm := rep.Binding.Members[0]\n\tif m.Account != \"july6netra_groq\" || m.Kind != \"openai\" || m.BaseURL != \"https://api.groq.com/openai/v1\" ||\n\t\tm.CredEnv != \"FAK_GROQ_API_KEY\" || m.UpstreamModel != \"qwen/qwen3.6-27b\" ||\n\t\tm.Local || m.EngineRoute != \"openai:july6netra_groq/qwen/qwen3.6-27b\" ||\n\t\tm.RequestsPerMinute != 30 || m.RequestsPerDay != 1000 ||\n\t\tm.TokensPerMinute != 8000 || m.TokensPerDay != 200000 {\n\t\tt.Fatalf(\"Groq Qwen3.6 binding wrong: %+v\\n%s\", m, out)\n\t}\n\tif strings.Contains(out, \"fake-groq-secret-value\") {\n\t\tt.Fatalf(\"route output leaked a Groq key value:\\n%s\", out)\n\t}\n}\n\nfunc TestRouteAccountsBindsGroqCompoundProfile(t *testing.T) {\n\tt.Setenv(\"FAK_GROQ_API_KEY\", \"fake-groq-secret-value\")\n\troster := writeDumpedRoster(t)\n\tmanifest := filepath.Join(t.TempDir(), \"groq-compound-route.json\")\n\tbody := `{\n \"version\": \"fak-route/v1\",\n \"default\": {\n \"members\": [{\"model\": \"groq-compound\", \"role\": \"primary\"}],\n \"reason\": \"Groq Compound lower-quality tier witness\"\n }\n}`\n\tif err := os.WriteFile(manifest, []byte(body), 0o600); err != nil {\n\t\tt.Fatalf(\"write manifest: %v\", err)\n\t}\n\n\tcode, out, errs := runRT(\"--manifest\", manifest, \"--accounts\", roster, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tBaseURL string `json:\"base_url\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t\tUpstreamModel string `json:\"upstream_model\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tRequestsPerMinute int `json:\"requests_per_minute\"`\n\t\t\t\tRequestsPerDay int `json:\"requests_per_day\"`\n\t\t\t\tTokensPerMinute int `json:\"tokens_per_minute\"`\n\t\t\t\tTokensPerDay int `json:\"tokens_per_day\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 1 {\n\t\tt.Fatalf(\"want one bound Groq Compound member, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tm := rep.Binding.Members[0]\n\tif m.Account != \"july6netra_groq_compound\" || m.Kind != \"openai\" || m.BaseURL != \"https://api.groq.com/openai/v1\" ||\n\t\tm.CredEnv != \"FAK_GROQ_API_KEY\" || m.UpstreamModel != \"groq/compound\" ||\n\t\tm.Local || m.EngineRoute != \"openai:july6netra_groq_compound/groq/compound\" ||\n\t\tm.RequestsPerMinute != 30 || m.RequestsPerDay != 250 ||\n\t\tm.TokensPerMinute != 0 || m.TokensPerDay != 0 {\n\t\tt.Fatalf(\"Groq Compound binding wrong: %+v\\n%s\", m, out)\n\t}\n\tif strings.Contains(out, \"fake-groq-secret-value\") {\n\t\tt.Fatalf(\"route output leaked a Groq key value:\\n%s\", out)\n\t}\n}\n\n// The CLI output (human and JSON) carries credential ENV-VAR NAMES, never secrets,\n// even when the env var holds a key.\nfunc TestRouteAccountsNeverPrintsSecret(t *testing.T) {\n\tt.Setenv(\"OPENAI_WORK_API_KEY\", \"sk-must-not-print\")\n\tpath := writeDumpedRoster(t)\n\tfor _, args := range [][]string{\n\t\t{\"--accounts-check\", path},\n\t\t{\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path},\n\t\t{\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path, \"--json\"},\n\t} {\n\t\t_, out, _ := runRT(args...)\n\t\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\t\tt.Fatalf(\"CLI leaked a secret for args %v:\\n%s\", args, out)\n\t\t}\n\t\tif !strings.Contains(out, \"OPENAI_WORK_API_KEY\") {\n\t\t\tt.Fatalf(\"CLI should show the env-var NAME for args %v:\\n%s\", args, out)\n\t\t}\n\t}\n}\n\n// A roster that fails validation (here, a local account with a remote base_url) is a\n// fail-loud error at --accounts-check, exit 1.\nfunc TestRouteAccountsCheckFailsLoud(t *testing.T) {\n\tbad := `{\"version\":\"fak-accounts/v1\",\"accounts\":[{\"id\":\"l\",\"kind\":\"local\",\"base_url\":\"https://api.openai.com/v1\"}]}`\n\tpath := filepath.Join(t.TempDir(), \"bad.json\")\n\tif err := os.WriteFile(path, []byte(bad), 0o600); err != nil {\n\t\tt.Fatalf(\"write: %v\", err)\n\t}\n\tcode, _, errs := runRT(\"--accounts-check\", path)\n\tif code != 1 {\n\t\tt.Fatalf(\"a residency-bypass roster must exit 1, got %d (stderr=%s)\", code, errs)\n\t}\n}\n"}, "c99f9d32ca81593f379f2688d676f9071cefe2df": {"t": 1786068286.4990811, "n": 11784, "h": "9fb4f064631b5367", "c": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// writeDumpedRoster dumps the built-in account roster to a temp file and returns its\n// path - the same starter `fak route --accounts-dump` emits for a user to edit.\nfunc writeDumpedRoster(t *testing.T) string {\n\tt.Helper()\n\tcode, out, _ := runRT(\"--accounts-dump\")\n\tif code != 0 || !strings.Contains(out, \"fak-accounts/v1\") {\n\t\tt.Fatalf(\"--accounts-dump failed: code=%d out=%s\", code, out)\n\t}\n\tpath := filepath.Join(t.TempDir(), \"roster.json\")\n\tif err := os.WriteFile(path, []byte(out), 0o600); err != nil {\n\t\tt.Fatalf(\"write roster: %v\", err)\n\t}\n\treturn path\n}\n\n// --accounts-dump emits a valid roster, and --accounts-check accepts it and prints the\n// account + binding surface.\nfunc TestRouteAccountsDumpAndCheck(t *testing.T) {\n\tpath := writeDumpedRoster(t)\n\tcode, out, _ := runRT(\"--accounts-check\", path)\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-check exit=%d out=%s\", code, out)\n\t}\n\tfor _, want := range []string{\"roster valid\", \"accounts:\", \"credential readiness\", \"bindings\", \"residency\"} {\n\t\tif !strings.Contains(out, want) {\n\t\t\tt.Fatalf(\"check surface missing %q:\\n%s\", want, out)\n\t\t}\n\t}\n}\n\nfunc TestRouteAccountsStatusReportsEnvReadiness(t *testing.T) {\n\tt.Setenv(\"OPENAI_API_KEY\", \"sk-must-not-print\")\n\tt.Setenv(\"OPENAI_WORK_API_KEY\", \"\")\n\tpath := writeDumpedRoster(t)\n\n\tcode, out, errs := runRT(\"--accounts-status\", path)\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-status exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tfor _, want := range []string{\"fak route accounts status\", \"needs_credential\", \"not_required\", \"OPENAI_WORK_API_KEY\"} {\n\t\tif !strings.Contains(out, want) {\n\t\t\tt.Fatalf(\"accounts status missing %q:\\n%s\", want, out)\n\t\t}\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"accounts status leaked a secret:\\n%s\", out)\n\t}\n\n\tcode, out, errs = runRT(\"--accounts-status\", path, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-status --json exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tSchema string `json:\"schema\"`\n\t\tSummary struct {\n\t\t\tTotal int `json:\"total\"`\n\t\t\tNeedsCredential int `json:\"needs_credential\"`\n\t\t} `json:\"summary\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif rep.Schema != \"fak.modelroute.accounts.v1\" || rep.Summary.Total == 0 || rep.Summary.NeedsCredential == 0 {\n\t\tt.Fatalf(\"unexpected readiness json: %+v\\n%s\", rep, out)\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"accounts status json leaked a secret:\\n%s\", out)\n\t}\n}\n\n// Routing a write-shaped tool call through the roster resolves BOTH guard-ensemble\n// members to their accounts - and they land on DIFFERENT accounts (the mix-and-match).\nfunc TestRouteAccountsBindsEnsembleAcrossAccounts(t *testing.T) {\n\tpath := writeDumpedRoster(t)\n\tcode, out, _ := runRT(\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d out=%s\", code, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 2 {\n\t\tt.Fatalf(\"want 2 bound members, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tif rep.Binding.Members[0].Account == rep.Binding.Members[1].Account {\n\t\tt.Fatalf(\"guard ensemble should span two accounts, both = %q\", rep.Binding.Members[0].Account)\n\t}\n\t// Each remote member's engine route is residency-honest (carries its kind, not local:).\n\tfor _, m := range rep.Binding.Members {\n\t\tif !m.Local && strings.HasPrefix(m.EngineRoute, \"local:\") {\n\t\t\tt.Fatalf(\"a remote member must not carry a local: route: %+v\", m)\n\t\t}\n\t}\n}\n\nfunc TestRouteAccountsBindsDeepSeekProfile(t *testing.T) {\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-must-not-print\")\n\troster := writeAPIHostDeepSeekModelAccountsRoster(t, \"https://api.deepseek.com\", \"https://api.deepseek.com/anthropic\")\n\tmanifest := filepath.Join(t.TempDir(), \"deepseek-route.json\")\n\tbody := `{\n \"version\": \"fak-route/v1\",\n \"default\": {\n \"members\": [{\"model\": \"deepseek-pro\", \"role\": \"primary\"}],\n \"reason\": \"DeepSeek V4 Pro profile witness\"\n }\n}`\n\tif err := os.WriteFile(manifest, []byte(body), 0o600); err != nil {\n\t\tt.Fatalf(\"write manifest: %v\", err)\n\t}\n\n\tcode, out, errs := runRT(\"--manifest\", manifest, \"--accounts\", roster, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tBaseURL string `json:\"base_url\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t\tUpstreamModel string `json:\"upstream_model\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tContextTokens int `json:\"context_tokens\"`\n\t\t\t\tMaxOutputTokens int `json:\"max_output_tokens\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 1 {\n\t\tt.Fatalf(\"want one bound DeepSeek member, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tm := rep.Binding.Members[0]\n\tif m.Account != \"deepseek\" || m.Kind != \"deepseek\" || m.BaseURL != \"https://api.deepseek.com\" ||\n\t\tm.CredEnv != \"DEEPSEEK_API_KEY\" || m.UpstreamModel != \"deepseek-v4-pro\" ||\n\t\tm.Local || m.EngineRoute != \"deepseek:deepseek/deepseek-v4-pro\" ||\n\t\tm.ContextTokens != 1000000 || m.MaxOutputTokens != 384000 {\n\t\tt.Fatalf(\"DeepSeek binding wrong: %+v\\n%s\", m, out)\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"route output leaked a secret:\\n%s\", out)\n\t}\n}\n\nfunc TestRouteAccountsBindsGroqQwen36Profile(t *testing.T) {\n\tt.Setenv(\"FAK_GROQ_API_KEY\", \"fake-groq-secret-value\")\n\troster := writeDumpedRoster(t)\n\tmanifest := filepath.Join(t.TempDir(), \"groq-route.json\")\n\tbody := `{\n \"version\": \"fak-route/v1\",\n \"default\": {\n \"members\": [{\"model\": \"qwen36-groq\", \"role\": \"primary\"}],\n \"reason\": \"Groq Qwen3.6 27B profile witness\"\n }\n}`\n\tif err := os.WriteFile(manifest, []byte(body), 0o600); err != nil {\n\t\tt.Fatalf(\"write manifest: %v\", err)\n\t}\n\n\tcode, out, errs := runRT(\"--manifest\", manifest, \"--accounts\", roster, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tBaseURL string `json:\"base_url\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t\tUpstreamModel string `json:\"upstream_model\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tRequestsPerMinute int `json:\"requests_per_minute\"`\n\t\t\t\tRequestsPerDay int `json:\"requests_per_day\"`\n\t\t\t\tTokensPerMinute int `json:\"tokens_per_minute\"`\n\t\t\t\tTokensPerDay int `json:\"tokens_per_day\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 1 {\n\t\tt.Fatalf(\"want one bound Groq member, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tm := rep.Binding.Members[0]\n\tif m.Account != \"july6netra_groq\" || m.Kind != \"openai\" || m.BaseURL != \"https://api.groq.com/openai/v1\" ||\n\t\tm.CredEnv != \"FAK_GROQ_API_KEY\" || m.UpstreamModel != \"qwen/qwen3.6-27b\" ||\n\t\tm.Local || m.EngineRoute != \"openai:july6netra_groq/qwen/qwen3.6-27b\" ||\n\t\tm.RequestsPerMinute != 30 || m.RequestsPerDay != 1000 ||\n\t\tm.TokensPerMinute != 8000 || m.TokensPerDay != 200000 {\n\t\tt.Fatalf(\"Groq Qwen3.6 binding wrong: %+v\\n%s\", m, out)\n\t}\n\tif strings.Contains(out, \"fake-groq-secret-value\") {\n\t\tt.Fatalf(\"route output leaked a Groq key value:\\n%s\", out)\n\t}\n}\n\nfunc TestRouteAccountsBindsGroqCompoundProfile(t *testing.T) {\n\tt.Setenv(\"FAK_GROQ_API_KEY\", \"fake-groq-secret-value\")\n\troster := writeDumpedRoster(t)\n\tmanifest := filepath.Join(t.TempDir(), \"groq-compound-route.json\")\n\tbody := `{\n \"version\": \"fak-route/v1\",\n \"default\": {\n \"members\": [{\"model\": \"groq-compound\", \"role\": \"primary\"}],\n \"reason\": \"Groq Compound lower-quality tier witness\"\n }\n}`\n\tif err := os.WriteFile(manifest, []byte(body), 0o600); err != nil {\n\t\tt.Fatalf(\"write manifest: %v\", err)\n\t}\n\n\tcode, out, errs := runRT(\"--manifest\", manifest, \"--accounts\", roster, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tBaseURL string `json:\"base_url\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t\tUpstreamModel string `json:\"upstream_model\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tRequestsPerMinute int `json:\"requests_per_minute\"`\n\t\t\t\tRequestsPerDay int `json:\"requests_per_day\"`\n\t\t\t\tTokensPerMinute int `json:\"tokens_per_minute\"`\n\t\t\t\tTokensPerDay int `json:\"tokens_per_day\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 1 {\n\t\tt.Fatalf(\"want one bound Groq Compound member, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tm := rep.Binding.Members[0]\n\tif m.Account != \"july6netra_groq_compound\" || m.Kind != \"openai\" || m.BaseURL != \"https://api.groq.com/openai/v1\" ||\n\t\tm.CredEnv != \"FAK_GROQ_API_KEY\" || m.UpstreamModel != \"groq/compound\" ||\n\t\tm.Local || m.EngineRoute != \"openai:july6netra_groq_compound/groq/compound\" ||\n\t\tm.RequestsPerMinute != 30 || m.RequestsPerDay != 250 ||\n\t\tm.TokensPerMinute != 0 || m.TokensPerDay != 0 {\n\t\tt.Fatalf(\"Groq Compound binding wrong: %+v\\n%s\", m, out)\n\t}\n\tif strings.Contains(out, \"fake-groq-secret-value\") {\n\t\tt.Fatalf(\"route output leaked a Groq key value:\\n%s\", out)\n\t}\n}\n\n// The CLI output (human and JSON) carries credential ENV-VAR NAMES, never secrets,\n// even when the env var holds a key.\nfunc TestRouteAccountsNeverPrintsSecret(t *testing.T) {\n\tt.Setenv(\"OPENAI_WORK_API_KEY\", \"sk-must-not-print\")\n\tpath := writeDumpedRoster(t)\n\tfor _, args := range [][]string{\n\t\t{\"--accounts-check\", path},\n\t\t{\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path},\n\t\t{\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path, \"--json\"},\n\t} {\n\t\t_, out, _ := runRT(args...)\n\t\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\t\tt.Fatalf(\"CLI leaked a secret for args %v:\\n%s\", args, out)\n\t\t}\n\t\tif !strings.Contains(out, \"OPENAI_WORK_API_KEY\") {\n\t\t\tt.Fatalf(\"CLI should show the env-var NAME for args %v:\\n%s\", args, out)\n\t\t}\n\t}\n}\n\n// A roster that fails validation (here, a local account with a remote base_url) is a\n// fail-loud error at --accounts-check, exit 1.\nfunc TestRouteAccountsCheckFailsLoud(t *testing.T) {\n\tbad := `{\"version\":\"fak-accounts/v1\",\"accounts\":[{\"id\":\"l\",\"kind\":\"local\",\"base_url\":\"https://api.openai.com/v1\"}]}`\n\tpath := filepath.Join(t.TempDir(), \"bad.json\")\n\tif err := os.WriteFile(path, []byte(bad), 0o600); err != nil {\n\t\tt.Fatalf(\"write: %v\", err)\n\t}\n\tcode, _, errs := runRT(\"--accounts-check\", path)\n\tif code != 1 {\n\t\tt.Fatalf(\"a residency-bypass roster must exit 1, got %d (stderr=%s)\", code, errs)\n\t}\n}\n"}, "cdad29dc07129694e98dccb7a97ca1403429dd9c": {"t": 1786068371.0334213, "n": 11784, "h": "9fb4f064631b5367", "c": "package main\n\nimport (\n\t\"encoding/json\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// writeDumpedRoster dumps the built-in account roster to a temp file and returns its\n// path - the same starter `fak route --accounts-dump` emits for a user to edit.\nfunc writeDumpedRoster(t *testing.T) string {\n\tt.Helper()\n\tcode, out, _ := runRT(\"--accounts-dump\")\n\tif code != 0 || !strings.Contains(out, \"fak-accounts/v1\") {\n\t\tt.Fatalf(\"--accounts-dump failed: code=%d out=%s\", code, out)\n\t}\n\tpath := filepath.Join(t.TempDir(), \"roster.json\")\n\tif err := os.WriteFile(path, []byte(out), 0o600); err != nil {\n\t\tt.Fatalf(\"write roster: %v\", err)\n\t}\n\treturn path\n}\n\n// --accounts-dump emits a valid roster, and --accounts-check accepts it and prints the\n// account + binding surface.\nfunc TestRouteAccountsDumpAndCheck(t *testing.T) {\n\tpath := writeDumpedRoster(t)\n\tcode, out, _ := runRT(\"--accounts-check\", path)\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-check exit=%d out=%s\", code, out)\n\t}\n\tfor _, want := range []string{\"roster valid\", \"accounts:\", \"credential readiness\", \"bindings\", \"residency\"} {\n\t\tif !strings.Contains(out, want) {\n\t\t\tt.Fatalf(\"check surface missing %q:\\n%s\", want, out)\n\t\t}\n\t}\n}\n\nfunc TestRouteAccountsStatusReportsEnvReadiness(t *testing.T) {\n\tt.Setenv(\"OPENAI_API_KEY\", \"sk-must-not-print\")\n\tt.Setenv(\"OPENAI_WORK_API_KEY\", \"\")\n\tpath := writeDumpedRoster(t)\n\n\tcode, out, errs := runRT(\"--accounts-status\", path)\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-status exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tfor _, want := range []string{\"fak route accounts status\", \"needs_credential\", \"not_required\", \"OPENAI_WORK_API_KEY\"} {\n\t\tif !strings.Contains(out, want) {\n\t\t\tt.Fatalf(\"accounts status missing %q:\\n%s\", want, out)\n\t\t}\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"accounts status leaked a secret:\\n%s\", out)\n\t}\n\n\tcode, out, errs = runRT(\"--accounts-status\", path, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"--accounts-status --json exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tSchema string `json:\"schema\"`\n\t\tSummary struct {\n\t\t\tTotal int `json:\"total\"`\n\t\t\tNeedsCredential int `json:\"needs_credential\"`\n\t\t} `json:\"summary\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif rep.Schema != \"fak.modelroute.accounts.v1\" || rep.Summary.Total == 0 || rep.Summary.NeedsCredential == 0 {\n\t\tt.Fatalf(\"unexpected readiness json: %+v\\n%s\", rep, out)\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"accounts status json leaked a secret:\\n%s\", out)\n\t}\n}\n\n// Routing a write-shaped tool call through the roster resolves BOTH guard-ensemble\n// members to their accounts - and they land on DIFFERENT accounts (the mix-and-match).\nfunc TestRouteAccountsBindsEnsembleAcrossAccounts(t *testing.T) {\n\tpath := writeDumpedRoster(t)\n\tcode, out, _ := runRT(\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d out=%s\", code, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 2 {\n\t\tt.Fatalf(\"want 2 bound members, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tif rep.Binding.Members[0].Account == rep.Binding.Members[1].Account {\n\t\tt.Fatalf(\"guard ensemble should span two accounts, both = %q\", rep.Binding.Members[0].Account)\n\t}\n\t// Each remote member's engine route is residency-honest (carries its kind, not local:).\n\tfor _, m := range rep.Binding.Members {\n\t\tif !m.Local && strings.HasPrefix(m.EngineRoute, \"local:\") {\n\t\t\tt.Fatalf(\"a remote member must not carry a local: route: %+v\", m)\n\t\t}\n\t}\n}\n\nfunc TestRouteAccountsBindsDeepSeekProfile(t *testing.T) {\n\tt.Setenv(\"DEEPSEEK_API_KEY\", \"sk-must-not-print\")\n\troster := writeAPIHostDeepSeekModelAccountsRoster(t, \"https://api.deepseek.com\", \"https://api.deepseek.com/anthropic\")\n\tmanifest := filepath.Join(t.TempDir(), \"deepseek-route.json\")\n\tbody := `{\n \"version\": \"fak-route/v1\",\n \"default\": {\n \"members\": [{\"model\": \"deepseek-pro\", \"role\": \"primary\"}],\n \"reason\": \"DeepSeek V4 Pro profile witness\"\n }\n}`\n\tif err := os.WriteFile(manifest, []byte(body), 0o600); err != nil {\n\t\tt.Fatalf(\"write manifest: %v\", err)\n\t}\n\n\tcode, out, errs := runRT(\"--manifest\", manifest, \"--accounts\", roster, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tBaseURL string `json:\"base_url\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t\tUpstreamModel string `json:\"upstream_model\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tContextTokens int `json:\"context_tokens\"`\n\t\t\t\tMaxOutputTokens int `json:\"max_output_tokens\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 1 {\n\t\tt.Fatalf(\"want one bound DeepSeek member, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tm := rep.Binding.Members[0]\n\tif m.Account != \"deepseek\" || m.Kind != \"deepseek\" || m.BaseURL != \"https://api.deepseek.com\" ||\n\t\tm.CredEnv != \"DEEPSEEK_API_KEY\" || m.UpstreamModel != \"deepseek-v4-pro\" ||\n\t\tm.Local || m.EngineRoute != \"deepseek:deepseek/deepseek-v4-pro\" ||\n\t\tm.ContextTokens != 1000000 || m.MaxOutputTokens != 384000 {\n\t\tt.Fatalf(\"DeepSeek binding wrong: %+v\\n%s\", m, out)\n\t}\n\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\tt.Fatalf(\"route output leaked a secret:\\n%s\", out)\n\t}\n}\n\nfunc TestRouteAccountsBindsGroqQwen36Profile(t *testing.T) {\n\tt.Setenv(\"FAK_GROQ_API_KEY\", \"fake-groq-secret-value\")\n\troster := writeDumpedRoster(t)\n\tmanifest := filepath.Join(t.TempDir(), \"groq-route.json\")\n\tbody := `{\n \"version\": \"fak-route/v1\",\n \"default\": {\n \"members\": [{\"model\": \"qwen36-groq\", \"role\": \"primary\"}],\n \"reason\": \"Groq Qwen3.6 27B profile witness\"\n }\n}`\n\tif err := os.WriteFile(manifest, []byte(body), 0o600); err != nil {\n\t\tt.Fatalf(\"write manifest: %v\", err)\n\t}\n\n\tcode, out, errs := runRT(\"--manifest\", manifest, \"--accounts\", roster, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tBaseURL string `json:\"base_url\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t\tUpstreamModel string `json:\"upstream_model\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tRequestsPerMinute int `json:\"requests_per_minute\"`\n\t\t\t\tRequestsPerDay int `json:\"requests_per_day\"`\n\t\t\t\tTokensPerMinute int `json:\"tokens_per_minute\"`\n\t\t\t\tTokensPerDay int `json:\"tokens_per_day\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 1 {\n\t\tt.Fatalf(\"want one bound Groq member, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tm := rep.Binding.Members[0]\n\tif m.Account != \"july6netra_groq\" || m.Kind != \"openai\" || m.BaseURL != \"https://api.groq.com/openai/v1\" ||\n\t\tm.CredEnv != \"FAK_GROQ_API_KEY\" || m.UpstreamModel != \"qwen/qwen3.6-27b\" ||\n\t\tm.Local || m.EngineRoute != \"openai:july6netra_groq/qwen/qwen3.6-27b\" ||\n\t\tm.RequestsPerMinute != 30 || m.RequestsPerDay != 1000 ||\n\t\tm.TokensPerMinute != 8000 || m.TokensPerDay != 200000 {\n\t\tt.Fatalf(\"Groq Qwen3.6 binding wrong: %+v\\n%s\", m, out)\n\t}\n\tif strings.Contains(out, \"fake-groq-secret-value\") {\n\t\tt.Fatalf(\"route output leaked a Groq key value:\\n%s\", out)\n\t}\n}\n\nfunc TestRouteAccountsBindsGroqCompoundProfile(t *testing.T) {\n\tt.Setenv(\"FAK_GROQ_API_KEY\", \"fake-groq-secret-value\")\n\troster := writeDumpedRoster(t)\n\tmanifest := filepath.Join(t.TempDir(), \"groq-compound-route.json\")\n\tbody := `{\n \"version\": \"fak-route/v1\",\n \"default\": {\n \"members\": [{\"model\": \"groq-compound\", \"role\": \"primary\"}],\n \"reason\": \"Groq Compound lower-quality tier witness\"\n }\n}`\n\tif err := os.WriteFile(manifest, []byte(body), 0o600); err != nil {\n\t\tt.Fatalf(\"write manifest: %v\", err)\n\t}\n\n\tcode, out, errs := runRT(\"--manifest\", manifest, \"--accounts\", roster, \"--json\")\n\tif code != 0 {\n\t\tt.Fatalf(\"exit=%d stderr=%s out=%s\", code, errs, out)\n\t}\n\tvar rep struct {\n\t\tBinding struct {\n\t\t\tMembers []struct {\n\t\t\t\tModel string `json:\"model\"`\n\t\t\t\tAccount string `json:\"account\"`\n\t\t\t\tKind string `json:\"kind\"`\n\t\t\t\tBaseURL string `json:\"base_url\"`\n\t\t\t\tCredEnv string `json:\"cred_env\"`\n\t\t\t\tUpstreamModel string `json:\"upstream_model\"`\n\t\t\t\tLocal bool `json:\"local\"`\n\t\t\t\tEngineRoute string `json:\"engine_route\"`\n\t\t\t\tRequestsPerMinute int `json:\"requests_per_minute\"`\n\t\t\t\tRequestsPerDay int `json:\"requests_per_day\"`\n\t\t\t\tTokensPerMinute int `json:\"tokens_per_minute\"`\n\t\t\t\tTokensPerDay int `json:\"tokens_per_day\"`\n\t\t\t} `json:\"members\"`\n\t\t} `json:\"binding\"`\n\t}\n\tif err := json.Unmarshal([]byte(out), &rep); err != nil {\n\t\tt.Fatalf(\"json: %v\\n%s\", err, out)\n\t}\n\tif len(rep.Binding.Members) != 1 {\n\t\tt.Fatalf(\"want one bound Groq Compound member, got %d:\\n%s\", len(rep.Binding.Members), out)\n\t}\n\tm := rep.Binding.Members[0]\n\tif m.Account != \"july6netra_groq_compound\" || m.Kind != \"openai\" || m.BaseURL != \"https://api.groq.com/openai/v1\" ||\n\t\tm.CredEnv != \"FAK_GROQ_API_KEY\" || m.UpstreamModel != \"groq/compound\" ||\n\t\tm.Local || m.EngineRoute != \"openai:july6netra_groq_compound/groq/compound\" ||\n\t\tm.RequestsPerMinute != 30 || m.RequestsPerDay != 250 ||\n\t\tm.TokensPerMinute != 0 || m.TokensPerDay != 0 {\n\t\tt.Fatalf(\"Groq Compound binding wrong: %+v\\n%s\", m, out)\n\t}\n\tif strings.Contains(out, \"fake-groq-secret-value\") {\n\t\tt.Fatalf(\"route output leaked a Groq key value:\\n%s\", out)\n\t}\n}\n\n// The CLI output (human and JSON) carries credential ENV-VAR NAMES, never secrets,\n// even when the env var holds a key.\nfunc TestRouteAccountsNeverPrintsSecret(t *testing.T) {\n\tt.Setenv(\"OPENAI_WORK_API_KEY\", \"sk-must-not-print\")\n\tpath := writeDumpedRoster(t)\n\tfor _, args := range [][]string{\n\t\t{\"--accounts-check\", path},\n\t\t{\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path},\n\t\t{\"--aspect\", \"tool_call\", \"--tool\", \"write_file\", \"--accounts\", path, \"--json\"},\n\t} {\n\t\t_, out, _ := runRT(args...)\n\t\tif strings.Contains(out, \"sk-must-not-print\") {\n\t\t\tt.Fatalf(\"CLI leaked a secret for args %v:\\n%s\", args, out)\n\t\t}\n\t\tif !strings.Contains(out, \"OPENAI_WORK_API_KEY\") {\n\t\t\tt.Fatalf(\"CLI should show the env-var NAME for args %v:\\n%s\", args, out)\n\t\t}\n\t}\n}\n\n// A roster that fails validation (here, a local account with a remote base_url) is a\n// fail-loud error at --accounts-check, exit 1.\nfunc TestRouteAccountsCheckFailsLoud(t *testing.T) {\n\tbad := `{\"version\":\"fak-accounts/v1\",\"accounts\":[{\"id\":\"l\",\"kind\":\"local\",\"base_url\":\"https://api.openai.com/v1\"}]}`\n\tpath := filepath.Join(t.TempDir(), \"bad.json\")\n\tif err := os.WriteFile(path, []byte(bad), 0o600); err != nil {\n\t\tt.Fatalf(\"write: %v\", err)\n\t}\n\tcode, _, errs := runRT(\"--accounts-check\", path)\n\tif code != 1 {\n\t\tt.Fatalf(\"a residency-bypass roster must exit 1, got %d (stderr=%s)\", code, errs)\n\t}\n}\n"}}, "internal/gateway/deepseek_anthropic_test.go": {"f1c36fd45ecfb60dc33288bccf3477b30bbd53cf": {"t": 1785745651.1568558, "n": 15119, "h": "55ee9d8d393759e7", "c": "package gateway\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/anthony-chaudhary/fak/internal/modelroute\"\n)\n\n// TestDeepSeekAnthropicMessagesURL pins the endpoint composition: the DeepSeek route\n// composes the SAME `/anthropic` base + `/v1/messages` suffix the real-Anthropic\n// adapter uses, yielding the documented DeepSeek URL.\nfunc TestDeepSeekAnthropicMessagesURL(t *testing.T) {\n\tif DeepSeekAnthropicMessagesPath != \"/v1/messages\" {\n\t\tt.Fatalf(\"messages path drifted from the Anthropic adapter suffix: %q\", DeepSeekAnthropicMessagesPath)\n\t}\n\tprof, err := NewDeepSeekAnthropicProfile(\"k\", modelroute.DeepSeekV4ProModel, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tconst want = \"https://api.deepseek.com/anthropic/v1/messages\"\n\tif got := prof.MessagesURL(); got != want {\n\t\tt.Fatalf(\"MessagesURL = %q, want %q\", got, want)\n\t}\n\t// A base with a trailing slash must not double the separator.\n\tslashed := DeepSeekAnthropicProfile{BaseURL: \"https://api.deepseek.com/anthropic/\", Model: \"deepseek-v4-pro\"}\n\tif got := slashed.MessagesURL(); got != want {\n\t\tt.Fatalf(\"trailing-slash base composed %q, want %q\", got, want)\n\t}\n}\n\n// TestDeepSeekAnthropicResolveModel covers the explicit model-name policy: direct V4\n// ids pass through, Claude family names map by fak's own rule, [1m] is recognized, and\n// an unroutable name FAILS LOUD instead of silently defaulting to Flash.\nfunc TestDeepSeekAnthropicResolveModel(t *testing.T) {\n\tcases := []struct {\n\t\tin string\n\t\tmodel string\n\t\tsource DeepSeekAnthropicModelSource\n\t\thas1M bool\n\t\twantErr bool\n\t}{\n\t\t{in: \"deepseek-v4-pro\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelDirect},\n\t\t{in: \"deepseek-v4-flash\", model: \"deepseek-v4-flash\", source: DeepSeekAnthropicModelDirect},\n\t\t{in: \"deepseek-v4-pro[1m]\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelDirect, has1M: true},\n\t\t{in: \"claude-opus-4-8\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"claude-sonnet-4-5\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"claude-3-5-haiku\", model: \"deepseek-v4-flash\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"gpt-4o\", wantErr: true},\n\t\t{in: \"\", wantErr: true},\n\t}\n\tfor _, c := range cases {\n\t\tmodel, source, has1M, err := ResolveDeepSeekAnthropicModel(c.in)\n\t\tif c.wantErr {\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q): want error, got model=%q\", c.in, model)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q): unexpected error %v\", c.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif model != c.model || source != c.source || has1M != c.has1M {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q) = (%q,%q,%v), want (%q,%q,%v)\", c.in, model, source, has1M, c.model, c.source, c.has1M)\n\t\t}\n\t}\n}\n\n// TestDeepSeekAnthropicUnsupportedContentBlockRefused proves the fence refuses every\n// documented unsupported block with a CLOSED reason, and passes the supported set.\nfunc TestDeepSeekAnthropicUnsupportedContentBlockRefused(t *testing.T) {\n\tunsupported := map[string]string{\n\t\t\"image\": DeepSeekAnthropicUnsupportedImage,\n\t\t\"document\": DeepSeekAnthropicUnsupportedDocument,\n\t\t\"redacted_thinking\": DeepSeekAnthropicUnsupportedRedactedThinking,\n\t\t\"code_execution\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\t\"server_tool_use\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\t\"mcp_tool_use\": DeepSeekAnthropicUnsupportedMCP,\n\t\t\"mcp_tool_result\": DeepSeekAnthropicUnsupportedMCP,\n\t}\n\tfor blk, wantReason := range unsupported {\n\t\treason, bad := DeepSeekAnthropicUnsupportedBlockReason(blk)\n\t\tif !bad || reason != wantReason {\n\t\t\tt.Errorf(\"block %q: got (%q,%v), want (%q,true)\", blk, reason, bad, wantReason)\n\t\t}\n\t\t// Casing must not slip a block past the fence.\n\t\tif _, bad := DeepSeekAnthropicUnsupportedBlockReason(strings.ToUpper(blk)); !bad {\n\t\t\tt.Errorf(\"block %q upper-cased slipped past the fence\", blk)\n\t\t}\n\t\terr := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: blk}}}})\n\t\tif err == nil || !strings.Contains(err.Error(), wantReason) {\n\t\t\tt.Errorf(\"FenceContentBlocks(%q): want error naming %q, got %v\", blk, wantReason, err)\n\t\t}\n\t}\n\tfor _, ok := range []string{\"text\", \"tool_use\", \"tool_result\", \"thinking\"} {\n\t\tif _, bad := DeepSeekAnthropicUnsupportedBlockReason(ok); bad {\n\t\t\tt.Errorf(\"supported block %q wrongly refused\", ok)\n\t\t}\n\t}\n\t// A supported-only conversation passes clean.\n\tif err := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"text\", Text: \"hi\"}}}}); err != nil {\n\t\tt.Errorf(\"supported-only conversation refused: %v\", err)\n\t}\n\t// Multiple bad blocks are all named, once each.\n\terr := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{\n\t\t{Type: \"image\"}, {Type: \"image\"}, {Type: \"document\"}, {Type: \"text\"},\n\t}}})\n\tif err == nil || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedImage) || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedDocument) {\n\t\tt.Fatalf(\"multi-block refusal must name image AND document: %v\", err)\n\t}\n\tif strings.Count(err.Error(), DeepSeekAnthropicUnsupportedImage) != 1 {\n\t\tt.Errorf(\"duplicate image block reported more than once: %v\", err)\n\t}\n}\n\n// TestDeepSeekAnthropicEffort covers effort validation across the two levels, unset,\n// and a rejected typo. A canonical tier DeepSeek does not name is NOT a rejection any\n// more — it saturates to the nearest supported tier (#4069) — but a token off fak's\n// ladder entirely still fails loud, which is what the typo fence was ever for.\nfunc TestDeepSeekAnthropicEffort(t *testing.T) {\n\tfor _, ok := range []string{\"\", \"high\", \"max\", \"MAX\"} {\n\t\tif err := ValidateDeepSeekAnthropicEffort(ok); err != nil {\n\t\t\tt.Errorf(\"effort %q rejected: %v\", ok, err)\n\t\t}\n\t}\n\t// Canonical tiers outside DeepSeek's set now pass validation by saturating.\n\tfor _, ok := range []string{\"maximum\", \"low\", \"none\", \"xhigh\", \"ultracode\"} {\n\t\tif err := ValidateDeepSeekAnthropicEffort(ok); err != nil {\n\t\t\tt.Errorf(\"canonical effort %q rejected, want saturation: %v\", ok, err)\n\t\t}\n\t}\n\t// Off-ladder tokens carry no ordering to clamp with, so they still refuse.\n\tfor _, bad := range []string{\"extreme\", \"wat-9000\", \"hihg\"} {\n\t\tif err := ValidateDeepSeekAnthropicEffort(bad); err == nil {\n\t\t\tt.Errorf(\"off-ladder effort %q accepted, want rejection\", bad)\n\t\t}\n\t}\n}\n\n// TestDeepSeekAnthropicEffortSaturates pins the concrete clamp targets and, crucially,\n// that the SATURATED tier is what the profile carries — validation passing is not the\n// same claim as the right tier reaching the wire.\nfunc TestDeepSeekAnthropicEffortSaturates(t *testing.T) {\n\tfor _, tc := range []struct{ in, want string }{\n\t\t{\"\", \"\"}, // control stays unset\n\t\t{\"high\", \"high\"},\n\t\t{\"max\", \"max\"},\n\t\t{\"xhigh\", \"max\"}, // over-range: tie breaks upward\n\t\t{\"ultracode\", \"max\"}, // xhigh alias\n\t\t{\"maximum\", \"max\"}, // max alias\n\t\t{\"low\", \"high\"}, // under-range collapses up to the nearest\n\t\t{\"medium\", \"high\"},\n\t\t{\"none\", \"high\"},\n\t\t{\" XHigh \", \"max\"}, // normalized\n\t} {\n\t\tgot, err := ResolveDeepSeekAnthropicEffort(tc.in)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicEffort(%q) errored: %v\", tc.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicEffort(%q) = %q, want %q\", tc.in, got, tc.want)\n\t\t}\n\t\t// The profile must carry the saturated tier, not the caller's raw one.\n\t\tprof, err := NewDeepSeekAnthropicProfile(\"k\", \"deepseek-v4-pro\", tc.in)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"profile with effort %q refused: %v\", tc.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif prof.Effort != tc.want {\n\t\t\tt.Errorf(\"profile effort for %q = %q, want saturated %q\", tc.in, prof.Effort, tc.want)\n\t\t}\n\t}\n\t// An off-ladder effort still refuses the whole profile.\n\tif _, err := NewDeepSeekAnthropicProfile(\"k\", \"deepseek-v4-pro\", \"extreme\"); err == nil {\n\t\tt.Error(\"profile accepted off-ladder effort \\\"extreme\\\", want refusal\")\n\t}\n}\n\n// TestDeepSeekAnthropicCacheControlNotFakSaving fences the value-accounting risk: an\n// ignored cache_control on this route is NEVER a fak-managed saving.\nfunc TestDeepSeekAnthropicCacheControlNotFakSaving(t *testing.T) {\n\tif IsFakManagedCacheSaving() {\n\t\tt.Fatal(\"DeepSeek Anthropic route must never book cache_control as a fak-managed saving\")\n\t}\n\tif DeepSeekAnthropicCacheControlProvenance != \"provider-ignored\" {\n\t\tt.Fatalf(\"cache_control provenance = %q, want provider-ignored\", DeepSeekAnthropicCacheControlProvenance)\n\t}\n}\n\n// TestDeepSeekAnthropicOfflinePost drives the full offline round trip: the request\n// posts to the expected DeepSeek URL with x-api-key (and leaks the secret nowhere),\n// carries output_config.effort=max and thinking WITHOUT budget_tokens, and the\n// thinking-mode response blocks decode back.\nfunc TestDeepSeekAnthropicOfflinePost(t *testing.T) {\n\tconst secret = \"sk-deepseek-SECRET-should-not-leak\"\n\tvar gotPath, gotKey string\n\tvar gotBody map[string]any\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tgotPath = r.URL.Path\n\t\tgotKey = r.Header.Get(\"x-api-key\")\n\t\traw, _ := io.ReadAll(r.Body)\n\t\tif strings.Contains(r.URL.String(), secret) {\n\t\t\tt.Errorf(\"secret leaked into URL: %s\", r.URL.String())\n\t\t}\n\t\tif strings.Contains(string(raw), secret) {\n\t\t\tt.Errorf(\"secret leaked into request body\")\n\t\t}\n\t\tif r.Header.Get(\"Authorization\") != \"\" {\n\t\t\tt.Errorf(\"DeepSeek route must use x-api-key, not Authorization: %q\", r.Header.Get(\"Authorization\"))\n\t\t}\n\t\t_ = json.Unmarshal(raw, &gotBody)\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tio.WriteString(w, `{\n\t\t\t\"id\":\"msg_1\",\"model\":\"deepseek-v4-pro\",\"role\":\"assistant\",\n\t\t\t\"content\":[\n\t\t\t\t{\"type\":\"thinking\",\"thinking\":\"weighing the options\",\"signature\":\"sig-abc\"},\n\t\t\t\t{\"type\":\"text\",\"text\":\"done\"}\n\t\t\t],\n\t\t\t\"stop_reason\":\"end_turn\",\n\t\t\t\"usage\":{\"input_tokens\":10,\"output_tokens\":5,\"prompt_cache_hit_tokens\":8,\"prompt_cache_miss_tokens\":2}\n\t\t}`)\n\t}))\n\tdefer srv.Close()\n\n\tprof, err := NewDeepSeekAnthropicProfile(secret, \"claude-opus-4-8\", DeepSeekAnthropicEffortMax)\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tprof.BaseURL = srv.URL + \"/anthropic\" // point the /anthropic base at the offline server\n\tif prof.Model != \"deepseek-v4-pro\" {\n\t\tt.Fatalf(\"claude-opus should map to deepseek-v4-pro, got %q\", prof.Model)\n\t}\n\n\tresp, err := prof.PostMessages(context.Background(), srv.Client(),\n\t\t\"you are a coding agent\",\n\t\t[]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"text\", Text: \"fix the bug\"}}}},\n\t\t1024, true)\n\tif err != nil {\n\t\tt.Fatalf(\"PostMessages: %v\", err)\n\t}\n\n\tif gotPath != \"/anthropic/v1/messages\" {\n\t\tt.Errorf(\"posted to %q, want /anthropic/v1/messages\", gotPath)\n\t}\n\tif gotKey != secret {\n\t\tt.Errorf(\"x-api-key = %q, want the configured secret\", gotKey)\n\t}\n\t// output_config.effort=max present.\n\toc, _ := gotBody[\"output_config\"].(map[string]any)\n\tif oc == nil || oc[\"effort\"] != \"max\" {\n\t\tt.Errorf(\"request output_config.effort != max: %v\", gotBody[\"output_config\"])\n\t}\n\t// thinking enabled, budget_tokens NOT sent (DeepSeek ignores it).\n\tth, _ := gotBody[\"thinking\"].(map[string]any)\n\tif th == nil || th[\"type\"] != \"enabled\" {\n\t\tt.Errorf(\"request thinking not enabled: %v\", gotBody[\"thinking\"])\n\t}\n\tif _, has := th[\"budget_tokens\"]; has {\n\t\tt.Errorf(\"budget_tokens must not be sent (DeepSeek ignores it): %v\", th)\n\t}\n\t// Thinking-mode response blocks decode back.\n\tvar haveThinking, haveText bool\n\tfor _, b := range resp.Content {\n\t\tswitch b.Type {\n\t\tcase \"thinking\":\n\t\t\thaveThinking = b.Thinking == \"weighing the options\" && b.Signature == \"sig-abc\"\n\t\tcase \"text\":\n\t\t\thaveText = b.Text == \"done\"\n\t\t}\n\t}\n\tif !haveThinking || !haveText {\n\t\tt.Errorf(\"response blocks not decoded: thinking=%v text=%v (%+v)\", haveThinking, haveText, resp.Content)\n\t}\n\t// Provider cache counters are OBSERVED, not fak savings.\n\tif resp.Usage.PromptCacheHitTokens != 8 || IsFakManagedCacheSaving() {\n\t\tt.Errorf(\"cache hit tokens are provider-observed, never a fak saving: hit=%d\", resp.Usage.PromptCacheHitTokens)\n\t}\n}\n\n// TestDeepSeekAnthropicOfflinePostRefusesUnsupportedBlock proves the fence stops an\n// image block BEFORE it reaches the wire.\nfunc TestDeepSeekAnthropicOfflinePostRefusesUnsupportedBlock(t *testing.T) {\n\tvar reached bool\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\treached = true\n\t\tio.WriteString(w, `{}`)\n\t}))\n\tdefer srv.Close()\n\tprof, err := NewDeepSeekAnthropicProfile(\"k\", \"deepseek-v4-flash\", \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tprof.BaseURL = srv.URL + \"/anthropic\"\n\t_, err = prof.PostMessages(context.Background(), srv.Client(), \"\",\n\t\t[]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"image\"}}}}, 256, false)\n\tif err == nil || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedImage) {\n\t\tt.Fatalf(\"post with image block must be refused loud: %v\", err)\n\t}\n\tif reached {\n\t\tt.Fatal(\"unsupported block reached the upstream instead of being fenced\")\n\t}\n}\n\n// TestClaudeCodeDeepSeekRunbook proves the Claude Code env preset renders for both\n// Linux/macOS and Windows, references the operator-owned DEEPSEEK_API_KEY, and never\n// inlines a secret value.\nfunc TestClaudeCodeDeepSeekRunbook(t *testing.T) {\n\tenv, err := ClaudeCodeDeepSeekEnv(\"deepseek-v4-pro[1m]\")\n\tif err != nil {\n\t\tt.Fatalf(\"env: %v\", err)\n\t}\n\tif env[\"ANTHROPIC_BASE_URL\"] != modelroute.DeepSeekAnthropicBaseURL {\n\t\tt.Errorf(\"base url = %q\", env[\"ANTHROPIC_BASE_URL\"])\n\t}\n\tif env[\"ANTHROPIC_MODEL\"] != \"deepseek-v4-pro[1m]\" {\n\t\tt.Errorf(\"model = %q, want deepseek-v4-pro[1m]\", env[\"ANTHROPIC_MODEL\"])\n\t}\n\tif !strings.Contains(env[\"ANTHROPIC_API_KEY\"], modelroute.DeepSeekAPIKeyEnv) {\n\t\tt.Errorf(\"api key entry must reference %s, got %q\", modelroute.DeepSeekAPIKeyEnv, env[\"ANTHROPIC_API_KEY\"])\n\t}\n\n\tunix, err := ClaudeCodeDeepSeekRunbook(\"unix\", \"claude-opus-4-8\")\n\tif err != nil {\n\t\tt.Fatalf(\"unix runbook: %v\", err)\n\t}\n\twin, err := ClaudeCodeDeepSeekRunbook(\"windows\", \"claude-opus-4-8\")\n\tif err != nil {\n\t\tt.Fatalf(\"windows runbook: %v\", err)\n\t}\n\tjoinUnix, joinWin := strings.Join(unix, \"\\n\"), strings.Join(win, \"\\n\")\n\tif !strings.Contains(joinUnix, \"export ANTHROPIC_BASE_URL=\") {\n\t\tt.Errorf(\"unix runbook missing export lines:\\n%s\", joinUnix)\n\t}\n\tif !strings.Contains(joinWin, \"$env:ANTHROPIC_BASE_URL\") {\n\t\tt.Errorf(\"windows runbook missing $env: lines:\\n%s\", joinWin)\n\t}\n\tfor _, block := range []string{joinUnix, joinWin} {\n\t\tif !strings.Contains(block, modelroute.DeepSeekAPIKeyEnv) {\n\t\t\tt.Errorf(\"runbook must name the operator secret %s:\\n%s\", modelroute.DeepSeekAPIKeyEnv, block)\n\t\t}\n\t}\n\t// Windows form uses PowerShell's own env reference for the secret.\n\tif !strings.Contains(joinWin, \"$env:\"+modelroute.DeepSeekAPIKeyEnv) {\n\t\tt.Errorf(\"windows runbook should reference $env:%s:\\n%s\", modelroute.DeepSeekAPIKeyEnv, joinWin)\n\t}\n\tif _, err := ClaudeCodeDeepSeekRunbook(\"plan9\", \"deepseek-v4-pro\"); err == nil {\n\t\tt.Error(\"unknown platform should error\")\n\t}\n}\n"}, "6306acd745190fccfc6905301852597809367fed": {"t": 1785749569.6502762, "n": 15119, "h": "55ee9d8d393759e7", "c": "package gateway\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/anthony-chaudhary/fak/internal/modelroute\"\n)\n\n// TestDeepSeekAnthropicMessagesURL pins the endpoint composition: the DeepSeek route\n// composes the SAME `/anthropic` base + `/v1/messages` suffix the real-Anthropic\n// adapter uses, yielding the documented DeepSeek URL.\nfunc TestDeepSeekAnthropicMessagesURL(t *testing.T) {\n\tif DeepSeekAnthropicMessagesPath != \"/v1/messages\" {\n\t\tt.Fatalf(\"messages path drifted from the Anthropic adapter suffix: %q\", DeepSeekAnthropicMessagesPath)\n\t}\n\tprof, err := NewDeepSeekAnthropicProfile(\"k\", modelroute.DeepSeekV4ProModel, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tconst want = \"https://api.deepseek.com/anthropic/v1/messages\"\n\tif got := prof.MessagesURL(); got != want {\n\t\tt.Fatalf(\"MessagesURL = %q, want %q\", got, want)\n\t}\n\t// A base with a trailing slash must not double the separator.\n\tslashed := DeepSeekAnthropicProfile{BaseURL: \"https://api.deepseek.com/anthropic/\", Model: \"deepseek-v4-pro\"}\n\tif got := slashed.MessagesURL(); got != want {\n\t\tt.Fatalf(\"trailing-slash base composed %q, want %q\", got, want)\n\t}\n}\n\n// TestDeepSeekAnthropicResolveModel covers the explicit model-name policy: direct V4\n// ids pass through, Claude family names map by fak's own rule, [1m] is recognized, and\n// an unroutable name FAILS LOUD instead of silently defaulting to Flash.\nfunc TestDeepSeekAnthropicResolveModel(t *testing.T) {\n\tcases := []struct {\n\t\tin string\n\t\tmodel string\n\t\tsource DeepSeekAnthropicModelSource\n\t\thas1M bool\n\t\twantErr bool\n\t}{\n\t\t{in: \"deepseek-v4-pro\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelDirect},\n\t\t{in: \"deepseek-v4-flash\", model: \"deepseek-v4-flash\", source: DeepSeekAnthropicModelDirect},\n\t\t{in: \"deepseek-v4-pro[1m]\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelDirect, has1M: true},\n\t\t{in: \"claude-opus-4-8\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"claude-sonnet-4-5\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"claude-3-5-haiku\", model: \"deepseek-v4-flash\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"gpt-4o\", wantErr: true},\n\t\t{in: \"\", wantErr: true},\n\t}\n\tfor _, c := range cases {\n\t\tmodel, source, has1M, err := ResolveDeepSeekAnthropicModel(c.in)\n\t\tif c.wantErr {\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q): want error, got model=%q\", c.in, model)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q): unexpected error %v\", c.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif model != c.model || source != c.source || has1M != c.has1M {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q) = (%q,%q,%v), want (%q,%q,%v)\", c.in, model, source, has1M, c.model, c.source, c.has1M)\n\t\t}\n\t}\n}\n\n// TestDeepSeekAnthropicUnsupportedContentBlockRefused proves the fence refuses every\n// documented unsupported block with a CLOSED reason, and passes the supported set.\nfunc TestDeepSeekAnthropicUnsupportedContentBlockRefused(t *testing.T) {\n\tunsupported := map[string]string{\n\t\t\"image\": DeepSeekAnthropicUnsupportedImage,\n\t\t\"document\": DeepSeekAnthropicUnsupportedDocument,\n\t\t\"redacted_thinking\": DeepSeekAnthropicUnsupportedRedactedThinking,\n\t\t\"code_execution\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\t\"server_tool_use\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\t\"mcp_tool_use\": DeepSeekAnthropicUnsupportedMCP,\n\t\t\"mcp_tool_result\": DeepSeekAnthropicUnsupportedMCP,\n\t}\n\tfor blk, wantReason := range unsupported {\n\t\treason, bad := DeepSeekAnthropicUnsupportedBlockReason(blk)\n\t\tif !bad || reason != wantReason {\n\t\t\tt.Errorf(\"block %q: got (%q,%v), want (%q,true)\", blk, reason, bad, wantReason)\n\t\t}\n\t\t// Casing must not slip a block past the fence.\n\t\tif _, bad := DeepSeekAnthropicUnsupportedBlockReason(strings.ToUpper(blk)); !bad {\n\t\t\tt.Errorf(\"block %q upper-cased slipped past the fence\", blk)\n\t\t}\n\t\terr := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: blk}}}})\n\t\tif err == nil || !strings.Contains(err.Error(), wantReason) {\n\t\t\tt.Errorf(\"FenceContentBlocks(%q): want error naming %q, got %v\", blk, wantReason, err)\n\t\t}\n\t}\n\tfor _, ok := range []string{\"text\", \"tool_use\", \"tool_result\", \"thinking\"} {\n\t\tif _, bad := DeepSeekAnthropicUnsupportedBlockReason(ok); bad {\n\t\t\tt.Errorf(\"supported block %q wrongly refused\", ok)\n\t\t}\n\t}\n\t// A supported-only conversation passes clean.\n\tif err := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"text\", Text: \"hi\"}}}}); err != nil {\n\t\tt.Errorf(\"supported-only conversation refused: %v\", err)\n\t}\n\t// Multiple bad blocks are all named, once each.\n\terr := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{\n\t\t{Type: \"image\"}, {Type: \"image\"}, {Type: \"document\"}, {Type: \"text\"},\n\t}}})\n\tif err == nil || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedImage) || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedDocument) {\n\t\tt.Fatalf(\"multi-block refusal must name image AND document: %v\", err)\n\t}\n\tif strings.Count(err.Error(), DeepSeekAnthropicUnsupportedImage) != 1 {\n\t\tt.Errorf(\"duplicate image block reported more than once: %v\", err)\n\t}\n}\n\n// TestDeepSeekAnthropicEffort covers effort validation across the two levels, unset,\n// and a rejected typo. A canonical tier DeepSeek does not name is NOT a rejection any\n// more — it saturates to the nearest supported tier (#4069) — but a token off fak's\n// ladder entirely still fails loud, which is what the typo fence was ever for.\nfunc TestDeepSeekAnthropicEffort(t *testing.T) {\n\tfor _, ok := range []string{\"\", \"high\", \"max\", \"MAX\"} {\n\t\tif err := ValidateDeepSeekAnthropicEffort(ok); err != nil {\n\t\t\tt.Errorf(\"effort %q rejected: %v\", ok, err)\n\t\t}\n\t}\n\t// Canonical tiers outside DeepSeek's set now pass validation by saturating.\n\tfor _, ok := range []string{\"maximum\", \"low\", \"none\", \"xhigh\", \"ultracode\"} {\n\t\tif err := ValidateDeepSeekAnthropicEffort(ok); err != nil {\n\t\t\tt.Errorf(\"canonical effort %q rejected, want saturation: %v\", ok, err)\n\t\t}\n\t}\n\t// Off-ladder tokens carry no ordering to clamp with, so they still refuse.\n\tfor _, bad := range []string{\"extreme\", \"wat-9000\", \"hihg\"} {\n\t\tif err := ValidateDeepSeekAnthropicEffort(bad); err == nil {\n\t\t\tt.Errorf(\"off-ladder effort %q accepted, want rejection\", bad)\n\t\t}\n\t}\n}\n\n// TestDeepSeekAnthropicEffortSaturates pins the concrete clamp targets and, crucially,\n// that the SATURATED tier is what the profile carries — validation passing is not the\n// same claim as the right tier reaching the wire.\nfunc TestDeepSeekAnthropicEffortSaturates(t *testing.T) {\n\tfor _, tc := range []struct{ in, want string }{\n\t\t{\"\", \"\"}, // control stays unset\n\t\t{\"high\", \"high\"},\n\t\t{\"max\", \"max\"},\n\t\t{\"xhigh\", \"max\"}, // over-range: tie breaks upward\n\t\t{\"ultracode\", \"max\"}, // xhigh alias\n\t\t{\"maximum\", \"max\"}, // max alias\n\t\t{\"low\", \"high\"}, // under-range collapses up to the nearest\n\t\t{\"medium\", \"high\"},\n\t\t{\"none\", \"high\"},\n\t\t{\" XHigh \", \"max\"}, // normalized\n\t} {\n\t\tgot, err := ResolveDeepSeekAnthropicEffort(tc.in)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicEffort(%q) errored: %v\", tc.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicEffort(%q) = %q, want %q\", tc.in, got, tc.want)\n\t\t}\n\t\t// The profile must carry the saturated tier, not the caller's raw one.\n\t\tprof, err := NewDeepSeekAnthropicProfile(\"k\", \"deepseek-v4-pro\", tc.in)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"profile with effort %q refused: %v\", tc.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif prof.Effort != tc.want {\n\t\t\tt.Errorf(\"profile effort for %q = %q, want saturated %q\", tc.in, prof.Effort, tc.want)\n\t\t}\n\t}\n\t// An off-ladder effort still refuses the whole profile.\n\tif _, err := NewDeepSeekAnthropicProfile(\"k\", \"deepseek-v4-pro\", \"extreme\"); err == nil {\n\t\tt.Error(\"profile accepted off-ladder effort \\\"extreme\\\", want refusal\")\n\t}\n}\n\n// TestDeepSeekAnthropicCacheControlNotFakSaving fences the value-accounting risk: an\n// ignored cache_control on this route is NEVER a fak-managed saving.\nfunc TestDeepSeekAnthropicCacheControlNotFakSaving(t *testing.T) {\n\tif IsFakManagedCacheSaving() {\n\t\tt.Fatal(\"DeepSeek Anthropic route must never book cache_control as a fak-managed saving\")\n\t}\n\tif DeepSeekAnthropicCacheControlProvenance != \"provider-ignored\" {\n\t\tt.Fatalf(\"cache_control provenance = %q, want provider-ignored\", DeepSeekAnthropicCacheControlProvenance)\n\t}\n}\n\n// TestDeepSeekAnthropicOfflinePost drives the full offline round trip: the request\n// posts to the expected DeepSeek URL with x-api-key (and leaks the secret nowhere),\n// carries output_config.effort=max and thinking WITHOUT budget_tokens, and the\n// thinking-mode response blocks decode back.\nfunc TestDeepSeekAnthropicOfflinePost(t *testing.T) {\n\tconst secret = \"sk-deepseek-SECRET-should-not-leak\"\n\tvar gotPath, gotKey string\n\tvar gotBody map[string]any\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tgotPath = r.URL.Path\n\t\tgotKey = r.Header.Get(\"x-api-key\")\n\t\traw, _ := io.ReadAll(r.Body)\n\t\tif strings.Contains(r.URL.String(), secret) {\n\t\t\tt.Errorf(\"secret leaked into URL: %s\", r.URL.String())\n\t\t}\n\t\tif strings.Contains(string(raw), secret) {\n\t\t\tt.Errorf(\"secret leaked into request body\")\n\t\t}\n\t\tif r.Header.Get(\"Authorization\") != \"\" {\n\t\t\tt.Errorf(\"DeepSeek route must use x-api-key, not Authorization: %q\", r.Header.Get(\"Authorization\"))\n\t\t}\n\t\t_ = json.Unmarshal(raw, &gotBody)\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tio.WriteString(w, `{\n\t\t\t\"id\":\"msg_1\",\"model\":\"deepseek-v4-pro\",\"role\":\"assistant\",\n\t\t\t\"content\":[\n\t\t\t\t{\"type\":\"thinking\",\"thinking\":\"weighing the options\",\"signature\":\"sig-abc\"},\n\t\t\t\t{\"type\":\"text\",\"text\":\"done\"}\n\t\t\t],\n\t\t\t\"stop_reason\":\"end_turn\",\n\t\t\t\"usage\":{\"input_tokens\":10,\"output_tokens\":5,\"prompt_cache_hit_tokens\":8,\"prompt_cache_miss_tokens\":2}\n\t\t}`)\n\t}))\n\tdefer srv.Close()\n\n\tprof, err := NewDeepSeekAnthropicProfile(secret, \"claude-opus-4-8\", DeepSeekAnthropicEffortMax)\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tprof.BaseURL = srv.URL + \"/anthropic\" // point the /anthropic base at the offline server\n\tif prof.Model != \"deepseek-v4-pro\" {\n\t\tt.Fatalf(\"claude-opus should map to deepseek-v4-pro, got %q\", prof.Model)\n\t}\n\n\tresp, err := prof.PostMessages(context.Background(), srv.Client(),\n\t\t\"you are a coding agent\",\n\t\t[]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"text\", Text: \"fix the bug\"}}}},\n\t\t1024, true)\n\tif err != nil {\n\t\tt.Fatalf(\"PostMessages: %v\", err)\n\t}\n\n\tif gotPath != \"/anthropic/v1/messages\" {\n\t\tt.Errorf(\"posted to %q, want /anthropic/v1/messages\", gotPath)\n\t}\n\tif gotKey != secret {\n\t\tt.Errorf(\"x-api-key = %q, want the configured secret\", gotKey)\n\t}\n\t// output_config.effort=max present.\n\toc, _ := gotBody[\"output_config\"].(map[string]any)\n\tif oc == nil || oc[\"effort\"] != \"max\" {\n\t\tt.Errorf(\"request output_config.effort != max: %v\", gotBody[\"output_config\"])\n\t}\n\t// thinking enabled, budget_tokens NOT sent (DeepSeek ignores it).\n\tth, _ := gotBody[\"thinking\"].(map[string]any)\n\tif th == nil || th[\"type\"] != \"enabled\" {\n\t\tt.Errorf(\"request thinking not enabled: %v\", gotBody[\"thinking\"])\n\t}\n\tif _, has := th[\"budget_tokens\"]; has {\n\t\tt.Errorf(\"budget_tokens must not be sent (DeepSeek ignores it): %v\", th)\n\t}\n\t// Thinking-mode response blocks decode back.\n\tvar haveThinking, haveText bool\n\tfor _, b := range resp.Content {\n\t\tswitch b.Type {\n\t\tcase \"thinking\":\n\t\t\thaveThinking = b.Thinking == \"weighing the options\" && b.Signature == \"sig-abc\"\n\t\tcase \"text\":\n\t\t\thaveText = b.Text == \"done\"\n\t\t}\n\t}\n\tif !haveThinking || !haveText {\n\t\tt.Errorf(\"response blocks not decoded: thinking=%v text=%v (%+v)\", haveThinking, haveText, resp.Content)\n\t}\n\t// Provider cache counters are OBSERVED, not fak savings.\n\tif resp.Usage.PromptCacheHitTokens != 8 || IsFakManagedCacheSaving() {\n\t\tt.Errorf(\"cache hit tokens are provider-observed, never a fak saving: hit=%d\", resp.Usage.PromptCacheHitTokens)\n\t}\n}\n\n// TestDeepSeekAnthropicOfflinePostRefusesUnsupportedBlock proves the fence stops an\n// image block BEFORE it reaches the wire.\nfunc TestDeepSeekAnthropicOfflinePostRefusesUnsupportedBlock(t *testing.T) {\n\tvar reached bool\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\treached = true\n\t\tio.WriteString(w, `{}`)\n\t}))\n\tdefer srv.Close()\n\tprof, err := NewDeepSeekAnthropicProfile(\"k\", \"deepseek-v4-flash\", \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tprof.BaseURL = srv.URL + \"/anthropic\"\n\t_, err = prof.PostMessages(context.Background(), srv.Client(), \"\",\n\t\t[]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"image\"}}}}, 256, false)\n\tif err == nil || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedImage) {\n\t\tt.Fatalf(\"post with image block must be refused loud: %v\", err)\n\t}\n\tif reached {\n\t\tt.Fatal(\"unsupported block reached the upstream instead of being fenced\")\n\t}\n}\n\n// TestClaudeCodeDeepSeekRunbook proves the Claude Code env preset renders for both\n// Linux/macOS and Windows, references the operator-owned DEEPSEEK_API_KEY, and never\n// inlines a secret value.\nfunc TestClaudeCodeDeepSeekRunbook(t *testing.T) {\n\tenv, err := ClaudeCodeDeepSeekEnv(\"deepseek-v4-pro[1m]\")\n\tif err != nil {\n\t\tt.Fatalf(\"env: %v\", err)\n\t}\n\tif env[\"ANTHROPIC_BASE_URL\"] != modelroute.DeepSeekAnthropicBaseURL {\n\t\tt.Errorf(\"base url = %q\", env[\"ANTHROPIC_BASE_URL\"])\n\t}\n\tif env[\"ANTHROPIC_MODEL\"] != \"deepseek-v4-pro[1m]\" {\n\t\tt.Errorf(\"model = %q, want deepseek-v4-pro[1m]\", env[\"ANTHROPIC_MODEL\"])\n\t}\n\tif !strings.Contains(env[\"ANTHROPIC_API_KEY\"], modelroute.DeepSeekAPIKeyEnv) {\n\t\tt.Errorf(\"api key entry must reference %s, got %q\", modelroute.DeepSeekAPIKeyEnv, env[\"ANTHROPIC_API_KEY\"])\n\t}\n\n\tunix, err := ClaudeCodeDeepSeekRunbook(\"unix\", \"claude-opus-4-8\")\n\tif err != nil {\n\t\tt.Fatalf(\"unix runbook: %v\", err)\n\t}\n\twin, err := ClaudeCodeDeepSeekRunbook(\"windows\", \"claude-opus-4-8\")\n\tif err != nil {\n\t\tt.Fatalf(\"windows runbook: %v\", err)\n\t}\n\tjoinUnix, joinWin := strings.Join(unix, \"\\n\"), strings.Join(win, \"\\n\")\n\tif !strings.Contains(joinUnix, \"export ANTHROPIC_BASE_URL=\") {\n\t\tt.Errorf(\"unix runbook missing export lines:\\n%s\", joinUnix)\n\t}\n\tif !strings.Contains(joinWin, \"$env:ANTHROPIC_BASE_URL\") {\n\t\tt.Errorf(\"windows runbook missing $env: lines:\\n%s\", joinWin)\n\t}\n\tfor _, block := range []string{joinUnix, joinWin} {\n\t\tif !strings.Contains(block, modelroute.DeepSeekAPIKeyEnv) {\n\t\t\tt.Errorf(\"runbook must name the operator secret %s:\\n%s\", modelroute.DeepSeekAPIKeyEnv, block)\n\t\t}\n\t}\n\t// Windows form uses PowerShell's own env reference for the secret.\n\tif !strings.Contains(joinWin, \"$env:\"+modelroute.DeepSeekAPIKeyEnv) {\n\t\tt.Errorf(\"windows runbook should reference $env:%s:\\n%s\", modelroute.DeepSeekAPIKeyEnv, joinWin)\n\t}\n\tif _, err := ClaudeCodeDeepSeekRunbook(\"plan9\", \"deepseek-v4-pro\"); err == nil {\n\t\tt.Error(\"unknown platform should error\")\n\t}\n}\n"}, "cacdefdc11d9927fabb5d874c3d6fec435976c40": {"t": 1785749570.7325852, "n": 12939, "h": "827db46d2e6567ae", "c": "package gateway\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/anthony-chaudhary/fak/internal/modelroute\"\n)\n\n// TestDeepSeekAnthropicMessagesURL pins the endpoint composition: the DeepSeek route\n// composes the SAME `/anthropic` base + `/v1/messages` suffix the real-Anthropic\n// adapter uses, yielding the documented DeepSeek URL.\nfunc TestDeepSeekAnthropicMessagesURL(t *testing.T) {\n\tif DeepSeekAnthropicMessagesPath != \"/v1/messages\" {\n\t\tt.Fatalf(\"messages path drifted from the Anthropic adapter suffix: %q\", DeepSeekAnthropicMessagesPath)\n\t}\n\tprof, err := NewDeepSeekAnthropicProfile(\"k\", modelroute.DeepSeekV4ProModel, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tconst want = \"https://api.deepseek.com/anthropic/v1/messages\"\n\tif got := prof.MessagesURL(); got != want {\n\t\tt.Fatalf(\"MessagesURL = %q, want %q\", got, want)\n\t}\n\t// A base with a trailing slash must not double the separator.\n\tslashed := DeepSeekAnthropicProfile{BaseURL: \"https://api.deepseek.com/anthropic/\", Model: \"deepseek-v4-pro\"}\n\tif got := slashed.MessagesURL(); got != want {\n\t\tt.Fatalf(\"trailing-slash base composed %q, want %q\", got, want)\n\t}\n}\n\n// TestDeepSeekAnthropicResolveModel covers the explicit model-name policy: direct V4\n// ids pass through, Claude family names map by fak's own rule, [1m] is recognized, and\n// an unroutable name FAILS LOUD instead of silently defaulting to Flash.\nfunc TestDeepSeekAnthropicResolveModel(t *testing.T) {\n\tcases := []struct {\n\t\tin string\n\t\tmodel string\n\t\tsource DeepSeekAnthropicModelSource\n\t\thas1M bool\n\t\twantErr bool\n\t}{\n\t\t{in: \"deepseek-v4-pro\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelDirect},\n\t\t{in: \"deepseek-v4-flash\", model: \"deepseek-v4-flash\", source: DeepSeekAnthropicModelDirect},\n\t\t{in: \"deepseek-v4-pro[1m]\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelDirect, has1M: true},\n\t\t{in: \"claude-opus-4-8\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"claude-sonnet-4-5\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"claude-3-5-haiku\", model: \"deepseek-v4-flash\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"gpt-4o\", wantErr: true},\n\t\t{in: \"\", wantErr: true},\n\t}\n\tfor _, c := range cases {\n\t\tmodel, source, has1M, err := ResolveDeepSeekAnthropicModel(c.in)\n\t\tif c.wantErr {\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q): want error, got model=%q\", c.in, model)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q): unexpected error %v\", c.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif model != c.model || source != c.source || has1M != c.has1M {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q) = (%q,%q,%v), want (%q,%q,%v)\", c.in, model, source, has1M, c.model, c.source, c.has1M)\n\t\t}\n\t}\n}\n\n// TestDeepSeekAnthropicUnsupportedContentBlockRefused proves the fence refuses every\n// documented unsupported block with a CLOSED reason, and passes the supported set.\nfunc TestDeepSeekAnthropicUnsupportedContentBlockRefused(t *testing.T) {\n\tunsupported := map[string]string{\n\t\t\"image\": DeepSeekAnthropicUnsupportedImage,\n\t\t\"document\": DeepSeekAnthropicUnsupportedDocument,\n\t\t\"redacted_thinking\": DeepSeekAnthropicUnsupportedRedactedThinking,\n\t\t\"code_execution\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\t\"server_tool_use\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\t\"mcp_tool_use\": DeepSeekAnthropicUnsupportedMCP,\n\t\t\"mcp_tool_result\": DeepSeekAnthropicUnsupportedMCP,\n\t}\n\tfor blk, wantReason := range unsupported {\n\t\treason, bad := DeepSeekAnthropicUnsupportedBlockReason(blk)\n\t\tif !bad || reason != wantReason {\n\t\t\tt.Errorf(\"block %q: got (%q,%v), want (%q,true)\", blk, reason, bad, wantReason)\n\t\t}\n\t\t// Casing must not slip a block past the fence.\n\t\tif _, bad := DeepSeekAnthropicUnsupportedBlockReason(strings.ToUpper(blk)); !bad {\n\t\t\tt.Errorf(\"block %q upper-cased slipped past the fence\", blk)\n\t\t}\n\t\terr := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: blk}}}})\n\t\tif err == nil || !strings.Contains(err.Error(), wantReason) {\n\t\t\tt.Errorf(\"FenceContentBlocks(%q): want error naming %q, got %v\", blk, wantReason, err)\n\t\t}\n\t}\n\tfor _, ok := range []string{\"text\", \"tool_use\", \"tool_result\", \"thinking\"} {\n\t\tif _, bad := DeepSeekAnthropicUnsupportedBlockReason(ok); bad {\n\t\t\tt.Errorf(\"supported block %q wrongly refused\", ok)\n\t\t}\n\t}\n\t// A supported-only conversation passes clean.\n\tif err := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"text\", Text: \"hi\"}}}}); err != nil {\n\t\tt.Errorf(\"supported-only conversation refused: %v\", err)\n\t}\n\t// Multiple bad blocks are all named, once each.\n\terr := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{\n\t\t{Type: \"image\"}, {Type: \"image\"}, {Type: \"document\"}, {Type: \"text\"},\n\t}}})\n\tif err == nil || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedImage) || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedDocument) {\n\t\tt.Fatalf(\"multi-block refusal must name image AND document: %v\", err)\n\t}\n\tif strings.Count(err.Error(), DeepSeekAnthropicUnsupportedImage) != 1 {\n\t\tt.Errorf(\"duplicate image block reported more than once: %v\", err)\n\t}\n}\n\n// TestDeepSeekAnthropicEffort covers effort validation across the two levels, unset,\n// and a rejected typo.\nfunc TestDeepSeekAnthropicEffort(t *testing.T) {\n\tfor _, ok := range []string{\"\", \"high\", \"max\", \"MAX\"} {\n\t\tif err := ValidateDeepSeekAnthropicEffort(ok); err != nil {\n\t\t\tt.Errorf(\"effort %q rejected: %v\", ok, err)\n\t\t}\n\t}\n\tfor _, bad := range []string{\"maximum\", \"low\", \"extreme\"} {\n\t\tif err := ValidateDeepSeekAnthropicEffort(bad); err == nil {\n\t\t\tt.Errorf(\"effort %q accepted, want rejection\", bad)\n\t\t}\n\t}\n}\n\n// TestDeepSeekAnthropicCacheControlNotFakSaving fences the value-accounting risk: an\n// ignored cache_control on this route is NEVER a fak-managed saving.\nfunc TestDeepSeekAnthropicCacheControlNotFakSaving(t *testing.T) {\n\tif IsFakManagedCacheSaving() {\n\t\tt.Fatal(\"DeepSeek Anthropic route must never book cache_control as a fak-managed saving\")\n\t}\n\tif DeepSeekAnthropicCacheControlProvenance != \"provider-ignored\" {\n\t\tt.Fatalf(\"cache_control provenance = %q, want provider-ignored\", DeepSeekAnthropicCacheControlProvenance)\n\t}\n}\n\n// TestDeepSeekAnthropicOfflinePost drives the full offline round trip: the request\n// posts to the expected DeepSeek URL with x-api-key (and leaks the secret nowhere),\n// carries output_config.effort=max and thinking WITHOUT budget_tokens, and the\n// thinking-mode response blocks decode back.\nfunc TestDeepSeekAnthropicOfflinePost(t *testing.T) {\n\tconst secret = \"sk-deepseek-SECRET-should-not-leak\"\n\tvar gotPath, gotKey string\n\tvar gotBody map[string]any\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tgotPath = r.URL.Path\n\t\tgotKey = r.Header.Get(\"x-api-key\")\n\t\traw, _ := io.ReadAll(r.Body)\n\t\tif strings.Contains(r.URL.String(), secret) {\n\t\t\tt.Errorf(\"secret leaked into URL: %s\", r.URL.String())\n\t\t}\n\t\tif strings.Contains(string(raw), secret) {\n\t\t\tt.Errorf(\"secret leaked into request body\")\n\t\t}\n\t\tif r.Header.Get(\"Authorization\") != \"\" {\n\t\t\tt.Errorf(\"DeepSeek route must use x-api-key, not Authorization: %q\", r.Header.Get(\"Authorization\"))\n\t\t}\n\t\t_ = json.Unmarshal(raw, &gotBody)\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tio.WriteString(w, `{\n\t\t\t\"id\":\"msg_1\",\"model\":\"deepseek-v4-pro\",\"role\":\"assistant\",\n\t\t\t\"content\":[\n\t\t\t\t{\"type\":\"thinking\",\"thinking\":\"weighing the options\",\"signature\":\"sig-abc\"},\n\t\t\t\t{\"type\":\"text\",\"text\":\"done\"}\n\t\t\t],\n\t\t\t\"stop_reason\":\"end_turn\",\n\t\t\t\"usage\":{\"input_tokens\":10,\"output_tokens\":5,\"prompt_cache_hit_tokens\":8,\"prompt_cache_miss_tokens\":2}\n\t\t}`)\n\t}))\n\tdefer srv.Close()\n\n\tprof, err := NewDeepSeekAnthropicProfile(secret, \"claude-opus-4-8\", DeepSeekAnthropicEffortMax)\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tprof.BaseURL = srv.URL + \"/anthropic\" // point the /anthropic base at the offline server\n\tif prof.Model != \"deepseek-v4-pro\" {\n\t\tt.Fatalf(\"claude-opus should map to deepseek-v4-pro, got %q\", prof.Model)\n\t}\n\n\tresp, err := prof.PostMessages(context.Background(), srv.Client(),\n\t\t\"you are a coding agent\",\n\t\t[]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"text\", Text: \"fix the bug\"}}}},\n\t\t1024, true)\n\tif err != nil {\n\t\tt.Fatalf(\"PostMessages: %v\", err)\n\t}\n\n\tif gotPath != \"/anthropic/v1/messages\" {\n\t\tt.Errorf(\"posted to %q, want /anthropic/v1/messages\", gotPath)\n\t}\n\tif gotKey != secret {\n\t\tt.Errorf(\"x-api-key = %q, want the configured secret\", gotKey)\n\t}\n\t// output_config.effort=max present.\n\toc, _ := gotBody[\"output_config\"].(map[string]any)\n\tif oc == nil || oc[\"effort\"] != \"max\" {\n\t\tt.Errorf(\"request output_config.effort != max: %v\", gotBody[\"output_config\"])\n\t}\n\t// thinking enabled, budget_tokens NOT sent (DeepSeek ignores it).\n\tth, _ := gotBody[\"thinking\"].(map[string]any)\n\tif th == nil || th[\"type\"] != \"enabled\" {\n\t\tt.Errorf(\"request thinking not enabled: %v\", gotBody[\"thinking\"])\n\t}\n\tif _, has := th[\"budget_tokens\"]; has {\n\t\tt.Errorf(\"budget_tokens must not be sent (DeepSeek ignores it): %v\", th)\n\t}\n\t// Thinking-mode response blocks decode back.\n\tvar haveThinking, haveText bool\n\tfor _, b := range resp.Content {\n\t\tswitch b.Type {\n\t\tcase \"thinking\":\n\t\t\thaveThinking = b.Thinking == \"weighing the options\" && b.Signature == \"sig-abc\"\n\t\tcase \"text\":\n\t\t\thaveText = b.Text == \"done\"\n\t\t}\n\t}\n\tif !haveThinking || !haveText {\n\t\tt.Errorf(\"response blocks not decoded: thinking=%v text=%v (%+v)\", haveThinking, haveText, resp.Content)\n\t}\n\t// Provider cache counters are OBSERVED, not fak savings.\n\tif resp.Usage.PromptCacheHitTokens != 8 || IsFakManagedCacheSaving() {\n\t\tt.Errorf(\"cache hit tokens are provider-observed, never a fak saving: hit=%d\", resp.Usage.PromptCacheHitTokens)\n\t}\n}\n\n// TestDeepSeekAnthropicOfflinePostRefusesUnsupportedBlock proves the fence stops an\n// image block BEFORE it reaches the wire.\nfunc TestDeepSeekAnthropicOfflinePostRefusesUnsupportedBlock(t *testing.T) {\n\tvar reached bool\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\treached = true\n\t\tio.WriteString(w, `{}`)\n\t}))\n\tdefer srv.Close()\n\tprof, err := NewDeepSeekAnthropicProfile(\"k\", \"deepseek-v4-flash\", \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tprof.BaseURL = srv.URL + \"/anthropic\"\n\t_, err = prof.PostMessages(context.Background(), srv.Client(), \"\",\n\t\t[]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"image\"}}}}, 256, false)\n\tif err == nil || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedImage) {\n\t\tt.Fatalf(\"post with image block must be refused loud: %v\", err)\n\t}\n\tif reached {\n\t\tt.Fatal(\"unsupported block reached the upstream instead of being fenced\")\n\t}\n}\n\n// TestClaudeCodeDeepSeekRunbook proves the Claude Code env preset renders for both\n// Linux/macOS and Windows, references the operator-owned DEEPSEEK_API_KEY, and never\n// inlines a secret value.\nfunc TestClaudeCodeDeepSeekRunbook(t *testing.T) {\n\tenv, err := ClaudeCodeDeepSeekEnv(\"deepseek-v4-pro[1m]\")\n\tif err != nil {\n\t\tt.Fatalf(\"env: %v\", err)\n\t}\n\tif env[\"ANTHROPIC_BASE_URL\"] != modelroute.DeepSeekAnthropicBaseURL {\n\t\tt.Errorf(\"base url = %q\", env[\"ANTHROPIC_BASE_URL\"])\n\t}\n\tif env[\"ANTHROPIC_MODEL\"] != \"deepseek-v4-pro[1m]\" {\n\t\tt.Errorf(\"model = %q, want deepseek-v4-pro[1m]\", env[\"ANTHROPIC_MODEL\"])\n\t}\n\tif !strings.Contains(env[\"ANTHROPIC_API_KEY\"], modelroute.DeepSeekAPIKeyEnv) {\n\t\tt.Errorf(\"api key entry must reference %s, got %q\", modelroute.DeepSeekAPIKeyEnv, env[\"ANTHROPIC_API_KEY\"])\n\t}\n\n\tunix, err := ClaudeCodeDeepSeekRunbook(\"unix\", \"claude-opus-4-8\")\n\tif err != nil {\n\t\tt.Fatalf(\"unix runbook: %v\", err)\n\t}\n\twin, err := ClaudeCodeDeepSeekRunbook(\"windows\", \"claude-opus-4-8\")\n\tif err != nil {\n\t\tt.Fatalf(\"windows runbook: %v\", err)\n\t}\n\tjoinUnix, joinWin := strings.Join(unix, \"\\n\"), strings.Join(win, \"\\n\")\n\tif !strings.Contains(joinUnix, \"export ANTHROPIC_BASE_URL=\") {\n\t\tt.Errorf(\"unix runbook missing export lines:\\n%s\", joinUnix)\n\t}\n\tif !strings.Contains(joinWin, \"$env:ANTHROPIC_BASE_URL\") {\n\t\tt.Errorf(\"windows runbook missing $env: lines:\\n%s\", joinWin)\n\t}\n\tfor _, block := range []string{joinUnix, joinWin} {\n\t\tif !strings.Contains(block, modelroute.DeepSeekAPIKeyEnv) {\n\t\t\tt.Errorf(\"runbook must name the operator secret %s:\\n%s\", modelroute.DeepSeekAPIKeyEnv, block)\n\t\t}\n\t}\n\t// Windows form uses PowerShell's own env reference for the secret.\n\tif !strings.Contains(joinWin, \"$env:\"+modelroute.DeepSeekAPIKeyEnv) {\n\t\tt.Errorf(\"windows runbook should reference $env:%s:\\n%s\", modelroute.DeepSeekAPIKeyEnv, joinWin)\n\t}\n\tif _, err := ClaudeCodeDeepSeekRunbook(\"plan9\", \"deepseek-v4-pro\"); err == nil {\n\t\tt.Error(\"unknown platform should error\")\n\t}\n}\n"}, "13f5f1a6e0391eb28bcbea759890fb009251e5d1": {"t": 1786067427.989199, "n": 15119, "h": "55ee9d8d393759e7", "c": "package gateway\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/anthony-chaudhary/fak/internal/modelroute\"\n)\n\n// TestDeepSeekAnthropicMessagesURL pins the endpoint composition: the DeepSeek route\n// composes the SAME `/anthropic` base + `/v1/messages` suffix the real-Anthropic\n// adapter uses, yielding the documented DeepSeek URL.\nfunc TestDeepSeekAnthropicMessagesURL(t *testing.T) {\n\tif DeepSeekAnthropicMessagesPath != \"/v1/messages\" {\n\t\tt.Fatalf(\"messages path drifted from the Anthropic adapter suffix: %q\", DeepSeekAnthropicMessagesPath)\n\t}\n\tprof, err := NewDeepSeekAnthropicProfile(\"k\", modelroute.DeepSeekV4ProModel, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tconst want = \"https://api.deepseek.com/anthropic/v1/messages\"\n\tif got := prof.MessagesURL(); got != want {\n\t\tt.Fatalf(\"MessagesURL = %q, want %q\", got, want)\n\t}\n\t// A base with a trailing slash must not double the separator.\n\tslashed := DeepSeekAnthropicProfile{BaseURL: \"https://api.deepseek.com/anthropic/\", Model: \"deepseek-v4-pro\"}\n\tif got := slashed.MessagesURL(); got != want {\n\t\tt.Fatalf(\"trailing-slash base composed %q, want %q\", got, want)\n\t}\n}\n\n// TestDeepSeekAnthropicResolveModel covers the explicit model-name policy: direct V4\n// ids pass through, Claude family names map by fak's own rule, [1m] is recognized, and\n// an unroutable name FAILS LOUD instead of silently defaulting to Flash.\nfunc TestDeepSeekAnthropicResolveModel(t *testing.T) {\n\tcases := []struct {\n\t\tin string\n\t\tmodel string\n\t\tsource DeepSeekAnthropicModelSource\n\t\thas1M bool\n\t\twantErr bool\n\t}{\n\t\t{in: \"deepseek-v4-pro\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelDirect},\n\t\t{in: \"deepseek-v4-flash\", model: \"deepseek-v4-flash\", source: DeepSeekAnthropicModelDirect},\n\t\t{in: \"deepseek-v4-pro[1m]\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelDirect, has1M: true},\n\t\t{in: \"claude-opus-4-8\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"claude-sonnet-4-5\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"claude-3-5-haiku\", model: \"deepseek-v4-flash\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"gpt-4o\", wantErr: true},\n\t\t{in: \"\", wantErr: true},\n\t}\n\tfor _, c := range cases {\n\t\tmodel, source, has1M, err := ResolveDeepSeekAnthropicModel(c.in)\n\t\tif c.wantErr {\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q): want error, got model=%q\", c.in, model)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q): unexpected error %v\", c.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif model != c.model || source != c.source || has1M != c.has1M {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q) = (%q,%q,%v), want (%q,%q,%v)\", c.in, model, source, has1M, c.model, c.source, c.has1M)\n\t\t}\n\t}\n}\n\n// TestDeepSeekAnthropicUnsupportedContentBlockRefused proves the fence refuses every\n// documented unsupported block with a CLOSED reason, and passes the supported set.\nfunc TestDeepSeekAnthropicUnsupportedContentBlockRefused(t *testing.T) {\n\tunsupported := map[string]string{\n\t\t\"image\": DeepSeekAnthropicUnsupportedImage,\n\t\t\"document\": DeepSeekAnthropicUnsupportedDocument,\n\t\t\"redacted_thinking\": DeepSeekAnthropicUnsupportedRedactedThinking,\n\t\t\"code_execution\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\t\"server_tool_use\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\t\"mcp_tool_use\": DeepSeekAnthropicUnsupportedMCP,\n\t\t\"mcp_tool_result\": DeepSeekAnthropicUnsupportedMCP,\n\t}\n\tfor blk, wantReason := range unsupported {\n\t\treason, bad := DeepSeekAnthropicUnsupportedBlockReason(blk)\n\t\tif !bad || reason != wantReason {\n\t\t\tt.Errorf(\"block %q: got (%q,%v), want (%q,true)\", blk, reason, bad, wantReason)\n\t\t}\n\t\t// Casing must not slip a block past the fence.\n\t\tif _, bad := DeepSeekAnthropicUnsupportedBlockReason(strings.ToUpper(blk)); !bad {\n\t\t\tt.Errorf(\"block %q upper-cased slipped past the fence\", blk)\n\t\t}\n\t\terr := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: blk}}}})\n\t\tif err == nil || !strings.Contains(err.Error(), wantReason) {\n\t\t\tt.Errorf(\"FenceContentBlocks(%q): want error naming %q, got %v\", blk, wantReason, err)\n\t\t}\n\t}\n\tfor _, ok := range []string{\"text\", \"tool_use\", \"tool_result\", \"thinking\"} {\n\t\tif _, bad := DeepSeekAnthropicUnsupportedBlockReason(ok); bad {\n\t\t\tt.Errorf(\"supported block %q wrongly refused\", ok)\n\t\t}\n\t}\n\t// A supported-only conversation passes clean.\n\tif err := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"text\", Text: \"hi\"}}}}); err != nil {\n\t\tt.Errorf(\"supported-only conversation refused: %v\", err)\n\t}\n\t// Multiple bad blocks are all named, once each.\n\terr := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{\n\t\t{Type: \"image\"}, {Type: \"image\"}, {Type: \"document\"}, {Type: \"text\"},\n\t}}})\n\tif err == nil || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedImage) || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedDocument) {\n\t\tt.Fatalf(\"multi-block refusal must name image AND document: %v\", err)\n\t}\n\tif strings.Count(err.Error(), DeepSeekAnthropicUnsupportedImage) != 1 {\n\t\tt.Errorf(\"duplicate image block reported more than once: %v\", err)\n\t}\n}\n\n// TestDeepSeekAnthropicEffort covers effort validation across the two levels, unset,\n// and a rejected typo. A canonical tier DeepSeek does not name is NOT a rejection any\n// more — it saturates to the nearest supported tier (#4069) — but a token off fak's\n// ladder entirely still fails loud, which is what the typo fence was ever for.\nfunc TestDeepSeekAnthropicEffort(t *testing.T) {\n\tfor _, ok := range []string{\"\", \"high\", \"max\", \"MAX\"} {\n\t\tif err := ValidateDeepSeekAnthropicEffort(ok); err != nil {\n\t\t\tt.Errorf(\"effort %q rejected: %v\", ok, err)\n\t\t}\n\t}\n\t// Canonical tiers outside DeepSeek's set now pass validation by saturating.\n\tfor _, ok := range []string{\"maximum\", \"low\", \"none\", \"xhigh\", \"ultracode\"} {\n\t\tif err := ValidateDeepSeekAnthropicEffort(ok); err != nil {\n\t\t\tt.Errorf(\"canonical effort %q rejected, want saturation: %v\", ok, err)\n\t\t}\n\t}\n\t// Off-ladder tokens carry no ordering to clamp with, so they still refuse.\n\tfor _, bad := range []string{\"extreme\", \"wat-9000\", \"hihg\"} {\n\t\tif err := ValidateDeepSeekAnthropicEffort(bad); err == nil {\n\t\t\tt.Errorf(\"off-ladder effort %q accepted, want rejection\", bad)\n\t\t}\n\t}\n}\n\n// TestDeepSeekAnthropicEffortSaturates pins the concrete clamp targets and, crucially,\n// that the SATURATED tier is what the profile carries — validation passing is not the\n// same claim as the right tier reaching the wire.\nfunc TestDeepSeekAnthropicEffortSaturates(t *testing.T) {\n\tfor _, tc := range []struct{ in, want string }{\n\t\t{\"\", \"\"}, // control stays unset\n\t\t{\"high\", \"high\"},\n\t\t{\"max\", \"max\"},\n\t\t{\"xhigh\", \"max\"}, // over-range: tie breaks upward\n\t\t{\"ultracode\", \"max\"}, // xhigh alias\n\t\t{\"maximum\", \"max\"}, // max alias\n\t\t{\"low\", \"high\"}, // under-range collapses up to the nearest\n\t\t{\"medium\", \"high\"},\n\t\t{\"none\", \"high\"},\n\t\t{\" XHigh \", \"max\"}, // normalized\n\t} {\n\t\tgot, err := ResolveDeepSeekAnthropicEffort(tc.in)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicEffort(%q) errored: %v\", tc.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicEffort(%q) = %q, want %q\", tc.in, got, tc.want)\n\t\t}\n\t\t// The profile must carry the saturated tier, not the caller's raw one.\n\t\tprof, err := NewDeepSeekAnthropicProfile(\"k\", \"deepseek-v4-pro\", tc.in)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"profile with effort %q refused: %v\", tc.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif prof.Effort != tc.want {\n\t\t\tt.Errorf(\"profile effort for %q = %q, want saturated %q\", tc.in, prof.Effort, tc.want)\n\t\t}\n\t}\n\t// An off-ladder effort still refuses the whole profile.\n\tif _, err := NewDeepSeekAnthropicProfile(\"k\", \"deepseek-v4-pro\", \"extreme\"); err == nil {\n\t\tt.Error(\"profile accepted off-ladder effort \\\"extreme\\\", want refusal\")\n\t}\n}\n\n// TestDeepSeekAnthropicCacheControlNotFakSaving fences the value-accounting risk: an\n// ignored cache_control on this route is NEVER a fak-managed saving.\nfunc TestDeepSeekAnthropicCacheControlNotFakSaving(t *testing.T) {\n\tif IsFakManagedCacheSaving() {\n\t\tt.Fatal(\"DeepSeek Anthropic route must never book cache_control as a fak-managed saving\")\n\t}\n\tif DeepSeekAnthropicCacheControlProvenance != \"provider-ignored\" {\n\t\tt.Fatalf(\"cache_control provenance = %q, want provider-ignored\", DeepSeekAnthropicCacheControlProvenance)\n\t}\n}\n\n// TestDeepSeekAnthropicOfflinePost drives the full offline round trip: the request\n// posts to the expected DeepSeek URL with x-api-key (and leaks the secret nowhere),\n// carries output_config.effort=max and thinking WITHOUT budget_tokens, and the\n// thinking-mode response blocks decode back.\nfunc TestDeepSeekAnthropicOfflinePost(t *testing.T) {\n\tconst secret = \"sk-deepseek-SECRET-should-not-leak\"\n\tvar gotPath, gotKey string\n\tvar gotBody map[string]any\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tgotPath = r.URL.Path\n\t\tgotKey = r.Header.Get(\"x-api-key\")\n\t\traw, _ := io.ReadAll(r.Body)\n\t\tif strings.Contains(r.URL.String(), secret) {\n\t\t\tt.Errorf(\"secret leaked into URL: %s\", r.URL.String())\n\t\t}\n\t\tif strings.Contains(string(raw), secret) {\n\t\t\tt.Errorf(\"secret leaked into request body\")\n\t\t}\n\t\tif r.Header.Get(\"Authorization\") != \"\" {\n\t\t\tt.Errorf(\"DeepSeek route must use x-api-key, not Authorization: %q\", r.Header.Get(\"Authorization\"))\n\t\t}\n\t\t_ = json.Unmarshal(raw, &gotBody)\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tio.WriteString(w, `{\n\t\t\t\"id\":\"msg_1\",\"model\":\"deepseek-v4-pro\",\"role\":\"assistant\",\n\t\t\t\"content\":[\n\t\t\t\t{\"type\":\"thinking\",\"thinking\":\"weighing the options\",\"signature\":\"sig-abc\"},\n\t\t\t\t{\"type\":\"text\",\"text\":\"done\"}\n\t\t\t],\n\t\t\t\"stop_reason\":\"end_turn\",\n\t\t\t\"usage\":{\"input_tokens\":10,\"output_tokens\":5,\"prompt_cache_hit_tokens\":8,\"prompt_cache_miss_tokens\":2}\n\t\t}`)\n\t}))\n\tdefer srv.Close()\n\n\tprof, err := NewDeepSeekAnthropicProfile(secret, \"claude-opus-4-8\", DeepSeekAnthropicEffortMax)\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tprof.BaseURL = srv.URL + \"/anthropic\" // point the /anthropic base at the offline server\n\tif prof.Model != \"deepseek-v4-pro\" {\n\t\tt.Fatalf(\"claude-opus should map to deepseek-v4-pro, got %q\", prof.Model)\n\t}\n\n\tresp, err := prof.PostMessages(context.Background(), srv.Client(),\n\t\t\"you are a coding agent\",\n\t\t[]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"text\", Text: \"fix the bug\"}}}},\n\t\t1024, true)\n\tif err != nil {\n\t\tt.Fatalf(\"PostMessages: %v\", err)\n\t}\n\n\tif gotPath != \"/anthropic/v1/messages\" {\n\t\tt.Errorf(\"posted to %q, want /anthropic/v1/messages\", gotPath)\n\t}\n\tif gotKey != secret {\n\t\tt.Errorf(\"x-api-key = %q, want the configured secret\", gotKey)\n\t}\n\t// output_config.effort=max present.\n\toc, _ := gotBody[\"output_config\"].(map[string]any)\n\tif oc == nil || oc[\"effort\"] != \"max\" {\n\t\tt.Errorf(\"request output_config.effort != max: %v\", gotBody[\"output_config\"])\n\t}\n\t// thinking enabled, budget_tokens NOT sent (DeepSeek ignores it).\n\tth, _ := gotBody[\"thinking\"].(map[string]any)\n\tif th == nil || th[\"type\"] != \"enabled\" {\n\t\tt.Errorf(\"request thinking not enabled: %v\", gotBody[\"thinking\"])\n\t}\n\tif _, has := th[\"budget_tokens\"]; has {\n\t\tt.Errorf(\"budget_tokens must not be sent (DeepSeek ignores it): %v\", th)\n\t}\n\t// Thinking-mode response blocks decode back.\n\tvar haveThinking, haveText bool\n\tfor _, b := range resp.Content {\n\t\tswitch b.Type {\n\t\tcase \"thinking\":\n\t\t\thaveThinking = b.Thinking == \"weighing the options\" && b.Signature == \"sig-abc\"\n\t\tcase \"text\":\n\t\t\thaveText = b.Text == \"done\"\n\t\t}\n\t}\n\tif !haveThinking || !haveText {\n\t\tt.Errorf(\"response blocks not decoded: thinking=%v text=%v (%+v)\", haveThinking, haveText, resp.Content)\n\t}\n\t// Provider cache counters are OBSERVED, not fak savings.\n\tif resp.Usage.PromptCacheHitTokens != 8 || IsFakManagedCacheSaving() {\n\t\tt.Errorf(\"cache hit tokens are provider-observed, never a fak saving: hit=%d\", resp.Usage.PromptCacheHitTokens)\n\t}\n}\n\n// TestDeepSeekAnthropicOfflinePostRefusesUnsupportedBlock proves the fence stops an\n// image block BEFORE it reaches the wire.\nfunc TestDeepSeekAnthropicOfflinePostRefusesUnsupportedBlock(t *testing.T) {\n\tvar reached bool\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\treached = true\n\t\tio.WriteString(w, `{}`)\n\t}))\n\tdefer srv.Close()\n\tprof, err := NewDeepSeekAnthropicProfile(\"k\", \"deepseek-v4-flash\", \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tprof.BaseURL = srv.URL + \"/anthropic\"\n\t_, err = prof.PostMessages(context.Background(), srv.Client(), \"\",\n\t\t[]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"image\"}}}}, 256, false)\n\tif err == nil || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedImage) {\n\t\tt.Fatalf(\"post with image block must be refused loud: %v\", err)\n\t}\n\tif reached {\n\t\tt.Fatal(\"unsupported block reached the upstream instead of being fenced\")\n\t}\n}\n\n// TestClaudeCodeDeepSeekRunbook proves the Claude Code env preset renders for both\n// Linux/macOS and Windows, references the operator-owned DEEPSEEK_API_KEY, and never\n// inlines a secret value.\nfunc TestClaudeCodeDeepSeekRunbook(t *testing.T) {\n\tenv, err := ClaudeCodeDeepSeekEnv(\"deepseek-v4-pro[1m]\")\n\tif err != nil {\n\t\tt.Fatalf(\"env: %v\", err)\n\t}\n\tif env[\"ANTHROPIC_BASE_URL\"] != modelroute.DeepSeekAnthropicBaseURL {\n\t\tt.Errorf(\"base url = %q\", env[\"ANTHROPIC_BASE_URL\"])\n\t}\n\tif env[\"ANTHROPIC_MODEL\"] != \"deepseek-v4-pro[1m]\" {\n\t\tt.Errorf(\"model = %q, want deepseek-v4-pro[1m]\", env[\"ANTHROPIC_MODEL\"])\n\t}\n\tif !strings.Contains(env[\"ANTHROPIC_API_KEY\"], modelroute.DeepSeekAPIKeyEnv) {\n\t\tt.Errorf(\"api key entry must reference %s, got %q\", modelroute.DeepSeekAPIKeyEnv, env[\"ANTHROPIC_API_KEY\"])\n\t}\n\n\tunix, err := ClaudeCodeDeepSeekRunbook(\"unix\", \"claude-opus-4-8\")\n\tif err != nil {\n\t\tt.Fatalf(\"unix runbook: %v\", err)\n\t}\n\twin, err := ClaudeCodeDeepSeekRunbook(\"windows\", \"claude-opus-4-8\")\n\tif err != nil {\n\t\tt.Fatalf(\"windows runbook: %v\", err)\n\t}\n\tjoinUnix, joinWin := strings.Join(unix, \"\\n\"), strings.Join(win, \"\\n\")\n\tif !strings.Contains(joinUnix, \"export ANTHROPIC_BASE_URL=\") {\n\t\tt.Errorf(\"unix runbook missing export lines:\\n%s\", joinUnix)\n\t}\n\tif !strings.Contains(joinWin, \"$env:ANTHROPIC_BASE_URL\") {\n\t\tt.Errorf(\"windows runbook missing $env: lines:\\n%s\", joinWin)\n\t}\n\tfor _, block := range []string{joinUnix, joinWin} {\n\t\tif !strings.Contains(block, modelroute.DeepSeekAPIKeyEnv) {\n\t\t\tt.Errorf(\"runbook must name the operator secret %s:\\n%s\", modelroute.DeepSeekAPIKeyEnv, block)\n\t\t}\n\t}\n\t// Windows form uses PowerShell's own env reference for the secret.\n\tif !strings.Contains(joinWin, \"$env:\"+modelroute.DeepSeekAPIKeyEnv) {\n\t\tt.Errorf(\"windows runbook should reference $env:%s:\\n%s\", modelroute.DeepSeekAPIKeyEnv, joinWin)\n\t}\n\tif _, err := ClaudeCodeDeepSeekRunbook(\"plan9\", \"deepseek-v4-pro\"); err == nil {\n\t\tt.Error(\"unknown platform should error\")\n\t}\n}\n"}, "c99f9d32ca81593f379f2688d676f9071cefe2df": {"t": 1786068289.8285656, "n": 15119, "h": "55ee9d8d393759e7", "c": "package gateway\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/anthony-chaudhary/fak/internal/modelroute\"\n)\n\n// TestDeepSeekAnthropicMessagesURL pins the endpoint composition: the DeepSeek route\n// composes the SAME `/anthropic` base + `/v1/messages` suffix the real-Anthropic\n// adapter uses, yielding the documented DeepSeek URL.\nfunc TestDeepSeekAnthropicMessagesURL(t *testing.T) {\n\tif DeepSeekAnthropicMessagesPath != \"/v1/messages\" {\n\t\tt.Fatalf(\"messages path drifted from the Anthropic adapter suffix: %q\", DeepSeekAnthropicMessagesPath)\n\t}\n\tprof, err := NewDeepSeekAnthropicProfile(\"k\", modelroute.DeepSeekV4ProModel, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tconst want = \"https://api.deepseek.com/anthropic/v1/messages\"\n\tif got := prof.MessagesURL(); got != want {\n\t\tt.Fatalf(\"MessagesURL = %q, want %q\", got, want)\n\t}\n\t// A base with a trailing slash must not double the separator.\n\tslashed := DeepSeekAnthropicProfile{BaseURL: \"https://api.deepseek.com/anthropic/\", Model: \"deepseek-v4-pro\"}\n\tif got := slashed.MessagesURL(); got != want {\n\t\tt.Fatalf(\"trailing-slash base composed %q, want %q\", got, want)\n\t}\n}\n\n// TestDeepSeekAnthropicResolveModel covers the explicit model-name policy: direct V4\n// ids pass through, Claude family names map by fak's own rule, [1m] is recognized, and\n// an unroutable name FAILS LOUD instead of silently defaulting to Flash.\nfunc TestDeepSeekAnthropicResolveModel(t *testing.T) {\n\tcases := []struct {\n\t\tin string\n\t\tmodel string\n\t\tsource DeepSeekAnthropicModelSource\n\t\thas1M bool\n\t\twantErr bool\n\t}{\n\t\t{in: \"deepseek-v4-pro\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelDirect},\n\t\t{in: \"deepseek-v4-flash\", model: \"deepseek-v4-flash\", source: DeepSeekAnthropicModelDirect},\n\t\t{in: \"deepseek-v4-pro[1m]\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelDirect, has1M: true},\n\t\t{in: \"claude-opus-4-8\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"claude-sonnet-4-5\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"claude-3-5-haiku\", model: \"deepseek-v4-flash\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"gpt-4o\", wantErr: true},\n\t\t{in: \"\", wantErr: true},\n\t}\n\tfor _, c := range cases {\n\t\tmodel, source, has1M, err := ResolveDeepSeekAnthropicModel(c.in)\n\t\tif c.wantErr {\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q): want error, got model=%q\", c.in, model)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q): unexpected error %v\", c.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif model != c.model || source != c.source || has1M != c.has1M {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q) = (%q,%q,%v), want (%q,%q,%v)\", c.in, model, source, has1M, c.model, c.source, c.has1M)\n\t\t}\n\t}\n}\n\n// TestDeepSeekAnthropicUnsupportedContentBlockRefused proves the fence refuses every\n// documented unsupported block with a CLOSED reason, and passes the supported set.\nfunc TestDeepSeekAnthropicUnsupportedContentBlockRefused(t *testing.T) {\n\tunsupported := map[string]string{\n\t\t\"image\": DeepSeekAnthropicUnsupportedImage,\n\t\t\"document\": DeepSeekAnthropicUnsupportedDocument,\n\t\t\"redacted_thinking\": DeepSeekAnthropicUnsupportedRedactedThinking,\n\t\t\"code_execution\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\t\"server_tool_use\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\t\"mcp_tool_use\": DeepSeekAnthropicUnsupportedMCP,\n\t\t\"mcp_tool_result\": DeepSeekAnthropicUnsupportedMCP,\n\t}\n\tfor blk, wantReason := range unsupported {\n\t\treason, bad := DeepSeekAnthropicUnsupportedBlockReason(blk)\n\t\tif !bad || reason != wantReason {\n\t\t\tt.Errorf(\"block %q: got (%q,%v), want (%q,true)\", blk, reason, bad, wantReason)\n\t\t}\n\t\t// Casing must not slip a block past the fence.\n\t\tif _, bad := DeepSeekAnthropicUnsupportedBlockReason(strings.ToUpper(blk)); !bad {\n\t\t\tt.Errorf(\"block %q upper-cased slipped past the fence\", blk)\n\t\t}\n\t\terr := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: blk}}}})\n\t\tif err == nil || !strings.Contains(err.Error(), wantReason) {\n\t\t\tt.Errorf(\"FenceContentBlocks(%q): want error naming %q, got %v\", blk, wantReason, err)\n\t\t}\n\t}\n\tfor _, ok := range []string{\"text\", \"tool_use\", \"tool_result\", \"thinking\"} {\n\t\tif _, bad := DeepSeekAnthropicUnsupportedBlockReason(ok); bad {\n\t\t\tt.Errorf(\"supported block %q wrongly refused\", ok)\n\t\t}\n\t}\n\t// A supported-only conversation passes clean.\n\tif err := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"text\", Text: \"hi\"}}}}); err != nil {\n\t\tt.Errorf(\"supported-only conversation refused: %v\", err)\n\t}\n\t// Multiple bad blocks are all named, once each.\n\terr := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{\n\t\t{Type: \"image\"}, {Type: \"image\"}, {Type: \"document\"}, {Type: \"text\"},\n\t}}})\n\tif err == nil || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedImage) || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedDocument) {\n\t\tt.Fatalf(\"multi-block refusal must name image AND document: %v\", err)\n\t}\n\tif strings.Count(err.Error(), DeepSeekAnthropicUnsupportedImage) != 1 {\n\t\tt.Errorf(\"duplicate image block reported more than once: %v\", err)\n\t}\n}\n\n// TestDeepSeekAnthropicEffort covers effort validation across the two levels, unset,\n// and a rejected typo. A canonical tier DeepSeek does not name is NOT a rejection any\n// more — it saturates to the nearest supported tier (#4069) — but a token off fak's\n// ladder entirely still fails loud, which is what the typo fence was ever for.\nfunc TestDeepSeekAnthropicEffort(t *testing.T) {\n\tfor _, ok := range []string{\"\", \"high\", \"max\", \"MAX\"} {\n\t\tif err := ValidateDeepSeekAnthropicEffort(ok); err != nil {\n\t\t\tt.Errorf(\"effort %q rejected: %v\", ok, err)\n\t\t}\n\t}\n\t// Canonical tiers outside DeepSeek's set now pass validation by saturating.\n\tfor _, ok := range []string{\"maximum\", \"low\", \"none\", \"xhigh\", \"ultracode\"} {\n\t\tif err := ValidateDeepSeekAnthropicEffort(ok); err != nil {\n\t\t\tt.Errorf(\"canonical effort %q rejected, want saturation: %v\", ok, err)\n\t\t}\n\t}\n\t// Off-ladder tokens carry no ordering to clamp with, so they still refuse.\n\tfor _, bad := range []string{\"extreme\", \"wat-9000\", \"hihg\"} {\n\t\tif err := ValidateDeepSeekAnthropicEffort(bad); err == nil {\n\t\t\tt.Errorf(\"off-ladder effort %q accepted, want rejection\", bad)\n\t\t}\n\t}\n}\n\n// TestDeepSeekAnthropicEffortSaturates pins the concrete clamp targets and, crucially,\n// that the SATURATED tier is what the profile carries — validation passing is not the\n// same claim as the right tier reaching the wire.\nfunc TestDeepSeekAnthropicEffortSaturates(t *testing.T) {\n\tfor _, tc := range []struct{ in, want string }{\n\t\t{\"\", \"\"}, // control stays unset\n\t\t{\"high\", \"high\"},\n\t\t{\"max\", \"max\"},\n\t\t{\"xhigh\", \"max\"}, // over-range: tie breaks upward\n\t\t{\"ultracode\", \"max\"}, // xhigh alias\n\t\t{\"maximum\", \"max\"}, // max alias\n\t\t{\"low\", \"high\"}, // under-range collapses up to the nearest\n\t\t{\"medium\", \"high\"},\n\t\t{\"none\", \"high\"},\n\t\t{\" XHigh \", \"max\"}, // normalized\n\t} {\n\t\tgot, err := ResolveDeepSeekAnthropicEffort(tc.in)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicEffort(%q) errored: %v\", tc.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicEffort(%q) = %q, want %q\", tc.in, got, tc.want)\n\t\t}\n\t\t// The profile must carry the saturated tier, not the caller's raw one.\n\t\tprof, err := NewDeepSeekAnthropicProfile(\"k\", \"deepseek-v4-pro\", tc.in)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"profile with effort %q refused: %v\", tc.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif prof.Effort != tc.want {\n\t\t\tt.Errorf(\"profile effort for %q = %q, want saturated %q\", tc.in, prof.Effort, tc.want)\n\t\t}\n\t}\n\t// An off-ladder effort still refuses the whole profile.\n\tif _, err := NewDeepSeekAnthropicProfile(\"k\", \"deepseek-v4-pro\", \"extreme\"); err == nil {\n\t\tt.Error(\"profile accepted off-ladder effort \\\"extreme\\\", want refusal\")\n\t}\n}\n\n// TestDeepSeekAnthropicCacheControlNotFakSaving fences the value-accounting risk: an\n// ignored cache_control on this route is NEVER a fak-managed saving.\nfunc TestDeepSeekAnthropicCacheControlNotFakSaving(t *testing.T) {\n\tif IsFakManagedCacheSaving() {\n\t\tt.Fatal(\"DeepSeek Anthropic route must never book cache_control as a fak-managed saving\")\n\t}\n\tif DeepSeekAnthropicCacheControlProvenance != \"provider-ignored\" {\n\t\tt.Fatalf(\"cache_control provenance = %q, want provider-ignored\", DeepSeekAnthropicCacheControlProvenance)\n\t}\n}\n\n// TestDeepSeekAnthropicOfflinePost drives the full offline round trip: the request\n// posts to the expected DeepSeek URL with x-api-key (and leaks the secret nowhere),\n// carries output_config.effort=max and thinking WITHOUT budget_tokens, and the\n// thinking-mode response blocks decode back.\nfunc TestDeepSeekAnthropicOfflinePost(t *testing.T) {\n\tconst secret = \"sk-deepseek-SECRET-should-not-leak\"\n\tvar gotPath, gotKey string\n\tvar gotBody map[string]any\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tgotPath = r.URL.Path\n\t\tgotKey = r.Header.Get(\"x-api-key\")\n\t\traw, _ := io.ReadAll(r.Body)\n\t\tif strings.Contains(r.URL.String(), secret) {\n\t\t\tt.Errorf(\"secret leaked into URL: %s\", r.URL.String())\n\t\t}\n\t\tif strings.Contains(string(raw), secret) {\n\t\t\tt.Errorf(\"secret leaked into request body\")\n\t\t}\n\t\tif r.Header.Get(\"Authorization\") != \"\" {\n\t\t\tt.Errorf(\"DeepSeek route must use x-api-key, not Authorization: %q\", r.Header.Get(\"Authorization\"))\n\t\t}\n\t\t_ = json.Unmarshal(raw, &gotBody)\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tio.WriteString(w, `{\n\t\t\t\"id\":\"msg_1\",\"model\":\"deepseek-v4-pro\",\"role\":\"assistant\",\n\t\t\t\"content\":[\n\t\t\t\t{\"type\":\"thinking\",\"thinking\":\"weighing the options\",\"signature\":\"sig-abc\"},\n\t\t\t\t{\"type\":\"text\",\"text\":\"done\"}\n\t\t\t],\n\t\t\t\"stop_reason\":\"end_turn\",\n\t\t\t\"usage\":{\"input_tokens\":10,\"output_tokens\":5,\"prompt_cache_hit_tokens\":8,\"prompt_cache_miss_tokens\":2}\n\t\t}`)\n\t}))\n\tdefer srv.Close()\n\n\tprof, err := NewDeepSeekAnthropicProfile(secret, \"claude-opus-4-8\", DeepSeekAnthropicEffortMax)\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tprof.BaseURL = srv.URL + \"/anthropic\" // point the /anthropic base at the offline server\n\tif prof.Model != \"deepseek-v4-pro\" {\n\t\tt.Fatalf(\"claude-opus should map to deepseek-v4-pro, got %q\", prof.Model)\n\t}\n\n\tresp, err := prof.PostMessages(context.Background(), srv.Client(),\n\t\t\"you are a coding agent\",\n\t\t[]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"text\", Text: \"fix the bug\"}}}},\n\t\t1024, true)\n\tif err != nil {\n\t\tt.Fatalf(\"PostMessages: %v\", err)\n\t}\n\n\tif gotPath != \"/anthropic/v1/messages\" {\n\t\tt.Errorf(\"posted to %q, want /anthropic/v1/messages\", gotPath)\n\t}\n\tif gotKey != secret {\n\t\tt.Errorf(\"x-api-key = %q, want the configured secret\", gotKey)\n\t}\n\t// output_config.effort=max present.\n\toc, _ := gotBody[\"output_config\"].(map[string]any)\n\tif oc == nil || oc[\"effort\"] != \"max\" {\n\t\tt.Errorf(\"request output_config.effort != max: %v\", gotBody[\"output_config\"])\n\t}\n\t// thinking enabled, budget_tokens NOT sent (DeepSeek ignores it).\n\tth, _ := gotBody[\"thinking\"].(map[string]any)\n\tif th == nil || th[\"type\"] != \"enabled\" {\n\t\tt.Errorf(\"request thinking not enabled: %v\", gotBody[\"thinking\"])\n\t}\n\tif _, has := th[\"budget_tokens\"]; has {\n\t\tt.Errorf(\"budget_tokens must not be sent (DeepSeek ignores it): %v\", th)\n\t}\n\t// Thinking-mode response blocks decode back.\n\tvar haveThinking, haveText bool\n\tfor _, b := range resp.Content {\n\t\tswitch b.Type {\n\t\tcase \"thinking\":\n\t\t\thaveThinking = b.Thinking == \"weighing the options\" && b.Signature == \"sig-abc\"\n\t\tcase \"text\":\n\t\t\thaveText = b.Text == \"done\"\n\t\t}\n\t}\n\tif !haveThinking || !haveText {\n\t\tt.Errorf(\"response blocks not decoded: thinking=%v text=%v (%+v)\", haveThinking, haveText, resp.Content)\n\t}\n\t// Provider cache counters are OBSERVED, not fak savings.\n\tif resp.Usage.PromptCacheHitTokens != 8 || IsFakManagedCacheSaving() {\n\t\tt.Errorf(\"cache hit tokens are provider-observed, never a fak saving: hit=%d\", resp.Usage.PromptCacheHitTokens)\n\t}\n}\n\n// TestDeepSeekAnthropicOfflinePostRefusesUnsupportedBlock proves the fence stops an\n// image block BEFORE it reaches the wire.\nfunc TestDeepSeekAnthropicOfflinePostRefusesUnsupportedBlock(t *testing.T) {\n\tvar reached bool\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\treached = true\n\t\tio.WriteString(w, `{}`)\n\t}))\n\tdefer srv.Close()\n\tprof, err := NewDeepSeekAnthropicProfile(\"k\", \"deepseek-v4-flash\", \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tprof.BaseURL = srv.URL + \"/anthropic\"\n\t_, err = prof.PostMessages(context.Background(), srv.Client(), \"\",\n\t\t[]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"image\"}}}}, 256, false)\n\tif err == nil || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedImage) {\n\t\tt.Fatalf(\"post with image block must be refused loud: %v\", err)\n\t}\n\tif reached {\n\t\tt.Fatal(\"unsupported block reached the upstream instead of being fenced\")\n\t}\n}\n\n// TestClaudeCodeDeepSeekRunbook proves the Claude Code env preset renders for both\n// Linux/macOS and Windows, references the operator-owned DEEPSEEK_API_KEY, and never\n// inlines a secret value.\nfunc TestClaudeCodeDeepSeekRunbook(t *testing.T) {\n\tenv, err := ClaudeCodeDeepSeekEnv(\"deepseek-v4-pro[1m]\")\n\tif err != nil {\n\t\tt.Fatalf(\"env: %v\", err)\n\t}\n\tif env[\"ANTHROPIC_BASE_URL\"] != modelroute.DeepSeekAnthropicBaseURL {\n\t\tt.Errorf(\"base url = %q\", env[\"ANTHROPIC_BASE_URL\"])\n\t}\n\tif env[\"ANTHROPIC_MODEL\"] != \"deepseek-v4-pro[1m]\" {\n\t\tt.Errorf(\"model = %q, want deepseek-v4-pro[1m]\", env[\"ANTHROPIC_MODEL\"])\n\t}\n\tif !strings.Contains(env[\"ANTHROPIC_API_KEY\"], modelroute.DeepSeekAPIKeyEnv) {\n\t\tt.Errorf(\"api key entry must reference %s, got %q\", modelroute.DeepSeekAPIKeyEnv, env[\"ANTHROPIC_API_KEY\"])\n\t}\n\n\tunix, err := ClaudeCodeDeepSeekRunbook(\"unix\", \"claude-opus-4-8\")\n\tif err != nil {\n\t\tt.Fatalf(\"unix runbook: %v\", err)\n\t}\n\twin, err := ClaudeCodeDeepSeekRunbook(\"windows\", \"claude-opus-4-8\")\n\tif err != nil {\n\t\tt.Fatalf(\"windows runbook: %v\", err)\n\t}\n\tjoinUnix, joinWin := strings.Join(unix, \"\\n\"), strings.Join(win, \"\\n\")\n\tif !strings.Contains(joinUnix, \"export ANTHROPIC_BASE_URL=\") {\n\t\tt.Errorf(\"unix runbook missing export lines:\\n%s\", joinUnix)\n\t}\n\tif !strings.Contains(joinWin, \"$env:ANTHROPIC_BASE_URL\") {\n\t\tt.Errorf(\"windows runbook missing $env: lines:\\n%s\", joinWin)\n\t}\n\tfor _, block := range []string{joinUnix, joinWin} {\n\t\tif !strings.Contains(block, modelroute.DeepSeekAPIKeyEnv) {\n\t\t\tt.Errorf(\"runbook must name the operator secret %s:\\n%s\", modelroute.DeepSeekAPIKeyEnv, block)\n\t\t}\n\t}\n\t// Windows form uses PowerShell's own env reference for the secret.\n\tif !strings.Contains(joinWin, \"$env:\"+modelroute.DeepSeekAPIKeyEnv) {\n\t\tt.Errorf(\"windows runbook should reference $env:%s:\\n%s\", modelroute.DeepSeekAPIKeyEnv, joinWin)\n\t}\n\tif _, err := ClaudeCodeDeepSeekRunbook(\"plan9\", \"deepseek-v4-pro\"); err == nil {\n\t\tt.Error(\"unknown platform should error\")\n\t}\n}\n"}, "cdad29dc07129694e98dccb7a97ca1403429dd9c": {"t": 1786068374.8533075, "n": 15119, "h": "55ee9d8d393759e7", "c": "package gateway\n\nimport (\n\t\"context\"\n\t\"encoding/json\"\n\t\"io\"\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/anthony-chaudhary/fak/internal/modelroute\"\n)\n\n// TestDeepSeekAnthropicMessagesURL pins the endpoint composition: the DeepSeek route\n// composes the SAME `/anthropic` base + `/v1/messages` suffix the real-Anthropic\n// adapter uses, yielding the documented DeepSeek URL.\nfunc TestDeepSeekAnthropicMessagesURL(t *testing.T) {\n\tif DeepSeekAnthropicMessagesPath != \"/v1/messages\" {\n\t\tt.Fatalf(\"messages path drifted from the Anthropic adapter suffix: %q\", DeepSeekAnthropicMessagesPath)\n\t}\n\tprof, err := NewDeepSeekAnthropicProfile(\"k\", modelroute.DeepSeekV4ProModel, \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tconst want = \"https://api.deepseek.com/anthropic/v1/messages\"\n\tif got := prof.MessagesURL(); got != want {\n\t\tt.Fatalf(\"MessagesURL = %q, want %q\", got, want)\n\t}\n\t// A base with a trailing slash must not double the separator.\n\tslashed := DeepSeekAnthropicProfile{BaseURL: \"https://api.deepseek.com/anthropic/\", Model: \"deepseek-v4-pro\"}\n\tif got := slashed.MessagesURL(); got != want {\n\t\tt.Fatalf(\"trailing-slash base composed %q, want %q\", got, want)\n\t}\n}\n\n// TestDeepSeekAnthropicResolveModel covers the explicit model-name policy: direct V4\n// ids pass through, Claude family names map by fak's own rule, [1m] is recognized, and\n// an unroutable name FAILS LOUD instead of silently defaulting to Flash.\nfunc TestDeepSeekAnthropicResolveModel(t *testing.T) {\n\tcases := []struct {\n\t\tin string\n\t\tmodel string\n\t\tsource DeepSeekAnthropicModelSource\n\t\thas1M bool\n\t\twantErr bool\n\t}{\n\t\t{in: \"deepseek-v4-pro\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelDirect},\n\t\t{in: \"deepseek-v4-flash\", model: \"deepseek-v4-flash\", source: DeepSeekAnthropicModelDirect},\n\t\t{in: \"deepseek-v4-pro[1m]\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelDirect, has1M: true},\n\t\t{in: \"claude-opus-4-8\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"claude-sonnet-4-5\", model: \"deepseek-v4-pro\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"claude-3-5-haiku\", model: \"deepseek-v4-flash\", source: DeepSeekAnthropicModelClaudeMapped},\n\t\t{in: \"gpt-4o\", wantErr: true},\n\t\t{in: \"\", wantErr: true},\n\t}\n\tfor _, c := range cases {\n\t\tmodel, source, has1M, err := ResolveDeepSeekAnthropicModel(c.in)\n\t\tif c.wantErr {\n\t\t\tif err == nil {\n\t\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q): want error, got model=%q\", c.in, model)\n\t\t\t}\n\t\t\tcontinue\n\t\t}\n\t\tif err != nil {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q): unexpected error %v\", c.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif model != c.model || source != c.source || has1M != c.has1M {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicModel(%q) = (%q,%q,%v), want (%q,%q,%v)\", c.in, model, source, has1M, c.model, c.source, c.has1M)\n\t\t}\n\t}\n}\n\n// TestDeepSeekAnthropicUnsupportedContentBlockRefused proves the fence refuses every\n// documented unsupported block with a CLOSED reason, and passes the supported set.\nfunc TestDeepSeekAnthropicUnsupportedContentBlockRefused(t *testing.T) {\n\tunsupported := map[string]string{\n\t\t\"image\": DeepSeekAnthropicUnsupportedImage,\n\t\t\"document\": DeepSeekAnthropicUnsupportedDocument,\n\t\t\"redacted_thinking\": DeepSeekAnthropicUnsupportedRedactedThinking,\n\t\t\"code_execution\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\t\"server_tool_use\": DeepSeekAnthropicUnsupportedCodeExecution,\n\t\t\"mcp_tool_use\": DeepSeekAnthropicUnsupportedMCP,\n\t\t\"mcp_tool_result\": DeepSeekAnthropicUnsupportedMCP,\n\t}\n\tfor blk, wantReason := range unsupported {\n\t\treason, bad := DeepSeekAnthropicUnsupportedBlockReason(blk)\n\t\tif !bad || reason != wantReason {\n\t\t\tt.Errorf(\"block %q: got (%q,%v), want (%q,true)\", blk, reason, bad, wantReason)\n\t\t}\n\t\t// Casing must not slip a block past the fence.\n\t\tif _, bad := DeepSeekAnthropicUnsupportedBlockReason(strings.ToUpper(blk)); !bad {\n\t\t\tt.Errorf(\"block %q upper-cased slipped past the fence\", blk)\n\t\t}\n\t\terr := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: blk}}}})\n\t\tif err == nil || !strings.Contains(err.Error(), wantReason) {\n\t\t\tt.Errorf(\"FenceContentBlocks(%q): want error naming %q, got %v\", blk, wantReason, err)\n\t\t}\n\t}\n\tfor _, ok := range []string{\"text\", \"tool_use\", \"tool_result\", \"thinking\"} {\n\t\tif _, bad := DeepSeekAnthropicUnsupportedBlockReason(ok); bad {\n\t\t\tt.Errorf(\"supported block %q wrongly refused\", ok)\n\t\t}\n\t}\n\t// A supported-only conversation passes clean.\n\tif err := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"text\", Text: \"hi\"}}}}); err != nil {\n\t\tt.Errorf(\"supported-only conversation refused: %v\", err)\n\t}\n\t// Multiple bad blocks are all named, once each.\n\terr := FenceContentBlocks([]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{\n\t\t{Type: \"image\"}, {Type: \"image\"}, {Type: \"document\"}, {Type: \"text\"},\n\t}}})\n\tif err == nil || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedImage) || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedDocument) {\n\t\tt.Fatalf(\"multi-block refusal must name image AND document: %v\", err)\n\t}\n\tif strings.Count(err.Error(), DeepSeekAnthropicUnsupportedImage) != 1 {\n\t\tt.Errorf(\"duplicate image block reported more than once: %v\", err)\n\t}\n}\n\n// TestDeepSeekAnthropicEffort covers effort validation across the two levels, unset,\n// and a rejected typo. A canonical tier DeepSeek does not name is NOT a rejection any\n// more — it saturates to the nearest supported tier (#4069) — but a token off fak's\n// ladder entirely still fails loud, which is what the typo fence was ever for.\nfunc TestDeepSeekAnthropicEffort(t *testing.T) {\n\tfor _, ok := range []string{\"\", \"high\", \"max\", \"MAX\"} {\n\t\tif err := ValidateDeepSeekAnthropicEffort(ok); err != nil {\n\t\t\tt.Errorf(\"effort %q rejected: %v\", ok, err)\n\t\t}\n\t}\n\t// Canonical tiers outside DeepSeek's set now pass validation by saturating.\n\tfor _, ok := range []string{\"maximum\", \"low\", \"none\", \"xhigh\", \"ultracode\"} {\n\t\tif err := ValidateDeepSeekAnthropicEffort(ok); err != nil {\n\t\t\tt.Errorf(\"canonical effort %q rejected, want saturation: %v\", ok, err)\n\t\t}\n\t}\n\t// Off-ladder tokens carry no ordering to clamp with, so they still refuse.\n\tfor _, bad := range []string{\"extreme\", \"wat-9000\", \"hihg\"} {\n\t\tif err := ValidateDeepSeekAnthropicEffort(bad); err == nil {\n\t\t\tt.Errorf(\"off-ladder effort %q accepted, want rejection\", bad)\n\t\t}\n\t}\n}\n\n// TestDeepSeekAnthropicEffortSaturates pins the concrete clamp targets and, crucially,\n// that the SATURATED tier is what the profile carries — validation passing is not the\n// same claim as the right tier reaching the wire.\nfunc TestDeepSeekAnthropicEffortSaturates(t *testing.T) {\n\tfor _, tc := range []struct{ in, want string }{\n\t\t{\"\", \"\"}, // control stays unset\n\t\t{\"high\", \"high\"},\n\t\t{\"max\", \"max\"},\n\t\t{\"xhigh\", \"max\"}, // over-range: tie breaks upward\n\t\t{\"ultracode\", \"max\"}, // xhigh alias\n\t\t{\"maximum\", \"max\"}, // max alias\n\t\t{\"low\", \"high\"}, // under-range collapses up to the nearest\n\t\t{\"medium\", \"high\"},\n\t\t{\"none\", \"high\"},\n\t\t{\" XHigh \", \"max\"}, // normalized\n\t} {\n\t\tgot, err := ResolveDeepSeekAnthropicEffort(tc.in)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicEffort(%q) errored: %v\", tc.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif got != tc.want {\n\t\t\tt.Errorf(\"ResolveDeepSeekAnthropicEffort(%q) = %q, want %q\", tc.in, got, tc.want)\n\t\t}\n\t\t// The profile must carry the saturated tier, not the caller's raw one.\n\t\tprof, err := NewDeepSeekAnthropicProfile(\"k\", \"deepseek-v4-pro\", tc.in)\n\t\tif err != nil {\n\t\t\tt.Errorf(\"profile with effort %q refused: %v\", tc.in, err)\n\t\t\tcontinue\n\t\t}\n\t\tif prof.Effort != tc.want {\n\t\t\tt.Errorf(\"profile effort for %q = %q, want saturated %q\", tc.in, prof.Effort, tc.want)\n\t\t}\n\t}\n\t// An off-ladder effort still refuses the whole profile.\n\tif _, err := NewDeepSeekAnthropicProfile(\"k\", \"deepseek-v4-pro\", \"extreme\"); err == nil {\n\t\tt.Error(\"profile accepted off-ladder effort \\\"extreme\\\", want refusal\")\n\t}\n}\n\n// TestDeepSeekAnthropicCacheControlNotFakSaving fences the value-accounting risk: an\n// ignored cache_control on this route is NEVER a fak-managed saving.\nfunc TestDeepSeekAnthropicCacheControlNotFakSaving(t *testing.T) {\n\tif IsFakManagedCacheSaving() {\n\t\tt.Fatal(\"DeepSeek Anthropic route must never book cache_control as a fak-managed saving\")\n\t}\n\tif DeepSeekAnthropicCacheControlProvenance != \"provider-ignored\" {\n\t\tt.Fatalf(\"cache_control provenance = %q, want provider-ignored\", DeepSeekAnthropicCacheControlProvenance)\n\t}\n}\n\n// TestDeepSeekAnthropicOfflinePost drives the full offline round trip: the request\n// posts to the expected DeepSeek URL with x-api-key (and leaks the secret nowhere),\n// carries output_config.effort=max and thinking WITHOUT budget_tokens, and the\n// thinking-mode response blocks decode back.\nfunc TestDeepSeekAnthropicOfflinePost(t *testing.T) {\n\tconst secret = \"sk-deepseek-SECRET-should-not-leak\"\n\tvar gotPath, gotKey string\n\tvar gotBody map[string]any\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\tgotPath = r.URL.Path\n\t\tgotKey = r.Header.Get(\"x-api-key\")\n\t\traw, _ := io.ReadAll(r.Body)\n\t\tif strings.Contains(r.URL.String(), secret) {\n\t\t\tt.Errorf(\"secret leaked into URL: %s\", r.URL.String())\n\t\t}\n\t\tif strings.Contains(string(raw), secret) {\n\t\t\tt.Errorf(\"secret leaked into request body\")\n\t\t}\n\t\tif r.Header.Get(\"Authorization\") != \"\" {\n\t\t\tt.Errorf(\"DeepSeek route must use x-api-key, not Authorization: %q\", r.Header.Get(\"Authorization\"))\n\t\t}\n\t\t_ = json.Unmarshal(raw, &gotBody)\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\tio.WriteString(w, `{\n\t\t\t\"id\":\"msg_1\",\"model\":\"deepseek-v4-pro\",\"role\":\"assistant\",\n\t\t\t\"content\":[\n\t\t\t\t{\"type\":\"thinking\",\"thinking\":\"weighing the options\",\"signature\":\"sig-abc\"},\n\t\t\t\t{\"type\":\"text\",\"text\":\"done\"}\n\t\t\t],\n\t\t\t\"stop_reason\":\"end_turn\",\n\t\t\t\"usage\":{\"input_tokens\":10,\"output_tokens\":5,\"prompt_cache_hit_tokens\":8,\"prompt_cache_miss_tokens\":2}\n\t\t}`)\n\t}))\n\tdefer srv.Close()\n\n\tprof, err := NewDeepSeekAnthropicProfile(secret, \"claude-opus-4-8\", DeepSeekAnthropicEffortMax)\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tprof.BaseURL = srv.URL + \"/anthropic\" // point the /anthropic base at the offline server\n\tif prof.Model != \"deepseek-v4-pro\" {\n\t\tt.Fatalf(\"claude-opus should map to deepseek-v4-pro, got %q\", prof.Model)\n\t}\n\n\tresp, err := prof.PostMessages(context.Background(), srv.Client(),\n\t\t\"you are a coding agent\",\n\t\t[]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"text\", Text: \"fix the bug\"}}}},\n\t\t1024, true)\n\tif err != nil {\n\t\tt.Fatalf(\"PostMessages: %v\", err)\n\t}\n\n\tif gotPath != \"/anthropic/v1/messages\" {\n\t\tt.Errorf(\"posted to %q, want /anthropic/v1/messages\", gotPath)\n\t}\n\tif gotKey != secret {\n\t\tt.Errorf(\"x-api-key = %q, want the configured secret\", gotKey)\n\t}\n\t// output_config.effort=max present.\n\toc, _ := gotBody[\"output_config\"].(map[string]any)\n\tif oc == nil || oc[\"effort\"] != \"max\" {\n\t\tt.Errorf(\"request output_config.effort != max: %v\", gotBody[\"output_config\"])\n\t}\n\t// thinking enabled, budget_tokens NOT sent (DeepSeek ignores it).\n\tth, _ := gotBody[\"thinking\"].(map[string]any)\n\tif th == nil || th[\"type\"] != \"enabled\" {\n\t\tt.Errorf(\"request thinking not enabled: %v\", gotBody[\"thinking\"])\n\t}\n\tif _, has := th[\"budget_tokens\"]; has {\n\t\tt.Errorf(\"budget_tokens must not be sent (DeepSeek ignores it): %v\", th)\n\t}\n\t// Thinking-mode response blocks decode back.\n\tvar haveThinking, haveText bool\n\tfor _, b := range resp.Content {\n\t\tswitch b.Type {\n\t\tcase \"thinking\":\n\t\t\thaveThinking = b.Thinking == \"weighing the options\" && b.Signature == \"sig-abc\"\n\t\tcase \"text\":\n\t\t\thaveText = b.Text == \"done\"\n\t\t}\n\t}\n\tif !haveThinking || !haveText {\n\t\tt.Errorf(\"response blocks not decoded: thinking=%v text=%v (%+v)\", haveThinking, haveText, resp.Content)\n\t}\n\t// Provider cache counters are OBSERVED, not fak savings.\n\tif resp.Usage.PromptCacheHitTokens != 8 || IsFakManagedCacheSaving() {\n\t\tt.Errorf(\"cache hit tokens are provider-observed, never a fak saving: hit=%d\", resp.Usage.PromptCacheHitTokens)\n\t}\n}\n\n// TestDeepSeekAnthropicOfflinePostRefusesUnsupportedBlock proves the fence stops an\n// image block BEFORE it reaches the wire.\nfunc TestDeepSeekAnthropicOfflinePostRefusesUnsupportedBlock(t *testing.T) {\n\tvar reached bool\n\tsrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\treached = true\n\t\tio.WriteString(w, `{}`)\n\t}))\n\tdefer srv.Close()\n\tprof, err := NewDeepSeekAnthropicProfile(\"k\", \"deepseek-v4-flash\", \"\")\n\tif err != nil {\n\t\tt.Fatalf(\"build profile: %v\", err)\n\t}\n\tprof.BaseURL = srv.URL + \"/anthropic\"\n\t_, err = prof.PostMessages(context.Background(), srv.Client(), \"\",\n\t\t[]DeepSeekAnthropicMessage{{Role: \"user\", Content: []DeepSeekAnthropicBlock{{Type: \"image\"}}}}, 256, false)\n\tif err == nil || !strings.Contains(err.Error(), DeepSeekAnthropicUnsupportedImage) {\n\t\tt.Fatalf(\"post with image block must be refused loud: %v\", err)\n\t}\n\tif reached {\n\t\tt.Fatal(\"unsupported block reached the upstream instead of being fenced\")\n\t}\n}\n\n// TestClaudeCodeDeepSeekRunbook proves the Claude Code env preset renders for both\n// Linux/macOS and Windows, references the operator-owned DEEPSEEK_API_KEY, and never\n// inlines a secret value.\nfunc TestClaudeCodeDeepSeekRunbook(t *testing.T) {\n\tenv, err := ClaudeCodeDeepSeekEnv(\"deepseek-v4-pro[1m]\")\n\tif err != nil {\n\t\tt.Fatalf(\"env: %v\", err)\n\t}\n\tif env[\"ANTHROPIC_BASE_URL\"] != modelroute.DeepSeekAnthropicBaseURL {\n\t\tt.Errorf(\"base url = %q\", env[\"ANTHROPIC_BASE_URL\"])\n\t}\n\tif env[\"ANTHROPIC_MODEL\"] != \"deepseek-v4-pro[1m]\" {\n\t\tt.Errorf(\"model = %q, want deepseek-v4-pro[1m]\", env[\"ANTHROPIC_MODEL\"])\n\t}\n\tif !strings.Contains(env[\"ANTHROPIC_API_KEY\"], modelroute.DeepSeekAPIKeyEnv) {\n\t\tt.Errorf(\"api key entry must reference %s, got %q\", modelroute.DeepSeekAPIKeyEnv, env[\"ANTHROPIC_API_KEY\"])\n\t}\n\n\tunix, err := ClaudeCodeDeepSeekRunbook(\"unix\", \"claude-opus-4-8\")\n\tif err != nil {\n\t\tt.Fatalf(\"unix runbook: %v\", err)\n\t}\n\twin, err := ClaudeCodeDeepSeekRunbook(\"windows\", \"claude-opus-4-8\")\n\tif err != nil {\n\t\tt.Fatalf(\"windows runbook: %v\", err)\n\t}\n\tjoinUnix, joinWin := strings.Join(unix, \"\\n\"), strings.Join(win, \"\\n\")\n\tif !strings.Contains(joinUnix, \"export ANTHROPIC_BASE_URL=\") {\n\t\tt.Errorf(\"unix runbook missing export lines:\\n%s\", joinUnix)\n\t}\n\tif !strings.Contains(joinWin, \"$env:ANTHROPIC_BASE_URL\") {\n\t\tt.Errorf(\"windows runbook missing $env: lines:\\n%s\", joinWin)\n\t}\n\tfor _, block := range []string{joinUnix, joinWin} {\n\t\tif !strings.Contains(block, modelroute.DeepSeekAPIKeyEnv) {\n\t\t\tt.Errorf(\"runbook must name the operator secret %s:\\n%s\", modelroute.DeepSeekAPIKeyEnv, block)\n\t\t}\n\t}\n\t// Windows form uses PowerShell's own env reference for the secret.\n\tif !strings.Contains(joinWin, \"$env:\"+modelroute.DeepSeekAPIKeyEnv) {\n\t\tt.Errorf(\"windows runbook should reference $env:%s:\\n%s\", modelroute.DeepSeekAPIKeyEnv, joinWin)\n\t}\n\tif _, err := ClaudeCodeDeepSeekRunbook(\"plan9\", \"deepseek-v4-pro\"); err == nil {\n\t\tt.Error(\"unknown platform should error\")\n\t}\n}\n"}}, "cmd/fak/bench_labtarget.go": {"f1c36fd45ecfb60dc33288bccf3477b30bbd53cf": {"t": 1785746057.1763337, "n": 2077, "h": "4c9bab10036e2f6a", "c": "package main\n\n// bench_labtarget.go — resolveBenchBaseURL: the one seam that lets a benchmark's\n// --base-url address lab GPU hardware BY NAME (@lab/) instead of a hand-copied\n// host:port, reusing the SAME readiness+latency-gated resolver that\n// `fak guard --remote-serve` uses (resolveGuardRemoteServe -> resolveLabTarget). This is\n// how `fak deepseekbench` / `fak swebench` / `fak webbench` are meant to \"use the lab\n// hardware properly\": a live run cannot start against a box that is not linkstate.Clear /\n// AdmitDispatch, nor against a latency-degraded route, and never needs a private box\n// coordinate pasted onto the command line.\n//\n// Wire mapping (why the /v1 re-append): the lab resolver normalizes a target's base_url\n// with any trailing /v1 STRIPPED (http://host:8000/v1 -> http://host:8000), whereas the\n// benchmark clients build \"/chat/completions\" (see\n// internal/deepseekbench.MeasureStreamed) — so a self-hosted vLLM/SGLang box needs the\n// /v1 root restored. guardOpenAIV1Base (guard_provider.go) does exactly that, idempotently.\n\nimport \"strings\"\n\n// resolveBenchBaseURL maps a benchmark --base-url onto a concrete OpenAI-compatible root.\n// A plain URL — the hosted-provider default (e.g. https://api.deepseek.com) or an explicit\n// http://host:8000/v1 — is returned UNCHANGED (passthrough), so existing usage is\n// untouched. An @lab/ alias is resolved through the readiness-gated lab-target seam\n// and normalized to the /v1 root the benchmark clients append \"/chat/completions\" to.\n//\n// Fails CLOSED: an unready / absent / not-found / latency-degraded lab target surfaces the\n// resolver's LAB_* structured refusal (empty string, non-nil error) rather than silently\n// falling back to a public endpoint — a benchmark never runs against a box the readiness\n// gate has not cleared.\nfunc resolveBenchBaseURL(raw string) (string, error) {\n\tif !isLabTargetAlias(strings.TrimSpace(raw)) {\n\t\treturn raw, nil\n\t}\n\tbase, err := resolveGuardRemoteServe(raw)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn guardOpenAIV1Base(base), nil\n}\n"}, "631fb83301f20c5b8ba1bdbe6762de63f5d2357a": {"t": 1785749567.823304, "n": 2077, "h": "4c9bab10036e2f6a", "c": "package main\n\n// bench_labtarget.go — resolveBenchBaseURL: the one seam that lets a benchmark's\n// --base-url address lab GPU hardware BY NAME (@lab/) instead of a hand-copied\n// host:port, reusing the SAME readiness+latency-gated resolver that\n// `fak guard --remote-serve` uses (resolveGuardRemoteServe -> resolveLabTarget). This is\n// how `fak deepseekbench` / `fak swebench` / `fak webbench` are meant to \"use the lab\n// hardware properly\": a live run cannot start against a box that is not linkstate.Clear /\n// AdmitDispatch, nor against a latency-degraded route, and never needs a private box\n// coordinate pasted onto the command line.\n//\n// Wire mapping (why the /v1 re-append): the lab resolver normalizes a target's base_url\n// with any trailing /v1 STRIPPED (http://host:8000/v1 -> http://host:8000), whereas the\n// benchmark clients build \"/chat/completions\" (see\n// internal/deepseekbench.MeasureStreamed) — so a self-hosted vLLM/SGLang box needs the\n// /v1 root restored. guardOpenAIV1Base (guard_provider.go) does exactly that, idempotently.\n\nimport \"strings\"\n\n// resolveBenchBaseURL maps a benchmark --base-url onto a concrete OpenAI-compatible root.\n// A plain URL — the hosted-provider default (e.g. https://api.deepseek.com) or an explicit\n// http://host:8000/v1 — is returned UNCHANGED (passthrough), so existing usage is\n// untouched. An @lab/ alias is resolved through the readiness-gated lab-target seam\n// and normalized to the /v1 root the benchmark clients append \"/chat/completions\" to.\n//\n// Fails CLOSED: an unready / absent / not-found / latency-degraded lab target surfaces the\n// resolver's LAB_* structured refusal (empty string, non-nil error) rather than silently\n// falling back to a public endpoint — a benchmark never runs against a box the readiness\n// gate has not cleared.\nfunc resolveBenchBaseURL(raw string) (string, error) {\n\tif !isLabTargetAlias(strings.TrimSpace(raw)) {\n\t\treturn raw, nil\n\t}\n\tbase, err := resolveGuardRemoteServe(raw)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn guardOpenAIV1Base(base), nil\n}\n"}, "13f5f1a6e0391eb28bcbea759890fb009251e5d1": {"t": 1786067568.897344, "n": 2077, "h": "4c9bab10036e2f6a", "c": "package main\n\n// bench_labtarget.go — resolveBenchBaseURL: the one seam that lets a benchmark's\n// --base-url address lab GPU hardware BY NAME (@lab/) instead of a hand-copied\n// host:port, reusing the SAME readiness+latency-gated resolver that\n// `fak guard --remote-serve` uses (resolveGuardRemoteServe -> resolveLabTarget). This is\n// how `fak deepseekbench` / `fak swebench` / `fak webbench` are meant to \"use the lab\n// hardware properly\": a live run cannot start against a box that is not linkstate.Clear /\n// AdmitDispatch, nor against a latency-degraded route, and never needs a private box\n// coordinate pasted onto the command line.\n//\n// Wire mapping (why the /v1 re-append): the lab resolver normalizes a target's base_url\n// with any trailing /v1 STRIPPED (http://host:8000/v1 -> http://host:8000), whereas the\n// benchmark clients build \"/chat/completions\" (see\n// internal/deepseekbench.MeasureStreamed) — so a self-hosted vLLM/SGLang box needs the\n// /v1 root restored. guardOpenAIV1Base (guard_provider.go) does exactly that, idempotently.\n\nimport \"strings\"\n\n// resolveBenchBaseURL maps a benchmark --base-url onto a concrete OpenAI-compatible root.\n// A plain URL — the hosted-provider default (e.g. https://api.deepseek.com) or an explicit\n// http://host:8000/v1 — is returned UNCHANGED (passthrough), so existing usage is\n// untouched. An @lab/ alias is resolved through the readiness-gated lab-target seam\n// and normalized to the /v1 root the benchmark clients append \"/chat/completions\" to.\n//\n// Fails CLOSED: an unready / absent / not-found / latency-degraded lab target surfaces the\n// resolver's LAB_* structured refusal (empty string, non-nil error) rather than silently\n// falling back to a public endpoint — a benchmark never runs against a box the readiness\n// gate has not cleared.\nfunc resolveBenchBaseURL(raw string) (string, error) {\n\tif !isLabTargetAlias(strings.TrimSpace(raw)) {\n\t\treturn raw, nil\n\t}\n\tbase, err := resolveGuardRemoteServe(raw)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn guardOpenAIV1Base(base), nil\n}\n"}, "cdad29dc07129694e98dccb7a97ca1403429dd9c": {"t": 1786068370.115555, "n": 2077, "h": "4c9bab10036e2f6a", "c": "package main\n\n// bench_labtarget.go — resolveBenchBaseURL: the one seam that lets a benchmark's\n// --base-url address lab GPU hardware BY NAME (@lab/) instead of a hand-copied\n// host:port, reusing the SAME readiness+latency-gated resolver that\n// `fak guard --remote-serve` uses (resolveGuardRemoteServe -> resolveLabTarget). This is\n// how `fak deepseekbench` / `fak swebench` / `fak webbench` are meant to \"use the lab\n// hardware properly\": a live run cannot start against a box that is not linkstate.Clear /\n// AdmitDispatch, nor against a latency-degraded route, and never needs a private box\n// coordinate pasted onto the command line.\n//\n// Wire mapping (why the /v1 re-append): the lab resolver normalizes a target's base_url\n// with any trailing /v1 STRIPPED (http://host:8000/v1 -> http://host:8000), whereas the\n// benchmark clients build \"/chat/completions\" (see\n// internal/deepseekbench.MeasureStreamed) — so a self-hosted vLLM/SGLang box needs the\n// /v1 root restored. guardOpenAIV1Base (guard_provider.go) does exactly that, idempotently.\n\nimport \"strings\"\n\n// resolveBenchBaseURL maps a benchmark --base-url onto a concrete OpenAI-compatible root.\n// A plain URL — the hosted-provider default (e.g. https://api.deepseek.com) or an explicit\n// http://host:8000/v1 — is returned UNCHANGED (passthrough), so existing usage is\n// untouched. An @lab/ alias is resolved through the readiness-gated lab-target seam\n// and normalized to the /v1 root the benchmark clients append \"/chat/completions\" to.\n//\n// Fails CLOSED: an unready / absent / not-found / latency-degraded lab target surfaces the\n// resolver's LAB_* structured refusal (empty string, non-nil error) rather than silently\n// falling back to a public endpoint — a benchmark never runs against a box the readiness\n// gate has not cleared.\nfunc resolveBenchBaseURL(raw string) (string, error) {\n\tif !isLabTargetAlias(strings.TrimSpace(raw)) {\n\t\treturn raw, nil\n\t}\n\tbase, err := resolveGuardRemoteServe(raw)\n\tif err != nil {\n\t\treturn \"\", err\n\t}\n\treturn guardOpenAIV1Base(base), nil\n}\n"}}, "docs/supported/deepseek.md": {"f1c36fd45ecfb60dc33288bccf3477b30bbd53cf": {"t": 1785746495.1092815, "n": 10736, "h": "5e88f18cb5b667e8", "c": "---\ntitle: \"DeepSeek V4 through fak — hosted, Anthropic-compatible, and self-hosted routes\"\ndescription: \"The exact copy/paste commands for the three DeepSeek V4 routes fak fronts (hosted OpenAI-compatible, Anthropic-compatible for Claude Code-style clients, and self-hosted vLLM/SGLang/NIM), the thinking-mode caveats, the provider-relayed cache-counter stance, the self-hosted GPU-node bring-up path, and the prominent warning that the deepseek-chat / deepseek-reasoner aliases retire 2026-07-24 15:59 UTC.\"\n---\n\n# DeepSeek V4 through fak\n\nfak fronts DeepSeek as a wire, the same way it fronts any provider: it **governs\nand relays; it does not generate**. This page is the operator's exact-command\nreference for the three DeepSeek V4 routes, the thinking-mode caveats that bite in\npractice, and the honest limits. It is grounded in the official DeepSeek docs\n(researched **July 2026**, linked in [Sources](#sources-researched-july-2026)).\n\n> ## ⚠️ Alias retirement — `deepseek-chat` / `deepseek-reasoner` retire 2026-07-24 15:59 UTC\n>\n> The `deepseek-chat` and `deepseek-reasoner` model ids are **compatibility\n> aliases only** and retire on **2026-07-24 15:59 UTC**\n> ([vendor announcement](https://api-docs.deepseek.com/news/news260424)). Pin\n> **`deepseek-v4-pro`** (or `deepseek-v4-flash`) explicitly. Any script, account\n> profile, or harness config still naming an alias after the cutoff will break —\n> migrate now.\n\n## The three routes at a glance\n\n| Route | When to use it | Model id | Base URL |\n|---|---|---|---|\n| **Hosted OpenAI-compatible** | Any OpenAI-Chat-Completions client through `fak serve`/`fak guard` | `deepseek-v4-pro` | `https://api.deepseek.com` |\n| **Anthropic-compatible** | Claude Code-style clients that speak Anthropic Messages | `deepseek-v4-pro` | `https://api.deepseek.com/anthropic` |\n| **Self-hosted (vLLM/SGLang/NIM)** | Your own GPUs (Hopper/Blackwell-class node) | `deepseek-ai/DeepSeek-V4-Pro` / `-Flash` | `http://:8000/v1` |\n\nAll examples use the `DEEPSEEK_API_KEY` environment variable and **never a literal\nkey**.\n\n### 1. Hosted OpenAI-compatible route\n\nThe hosted DeepSeek API exposes the OpenAI Chat Completions surface. Front it with\nfak as a generic OpenAI-compatible upstream:\n\n```bash\nexport DEEPSEEK_API_KEY=... # your key; never commit it\n\nfak serve --provider openai-compatible \\\n --base-url \"https://api.deepseek.com\" \\\n --model \"deepseek-v4-pro\" \\\n --api-key-env DEEPSEEK_API_KEY\n```\n\nThe first request uses model `deepseek-v4-pro`, an OpenAI-compatible base URL, and\nsupports `thinking`, `reasoning_effort`, and the streaming toggle\n([vendor quick start](https://api-docs.deepseek.com/)).\n\n### 2. Anthropic-compatible route (Claude Code-style clients)\n\nDeepSeek also exposes an **Anthropic Messages**-compatible surface at\n`https://api.deepseek.com/anthropic`\n([coding-agents guide](https://api-docs.deepseek.com/guides/coding_agents)). A\nClaude Code-style client can point at fak, which fronts that surface:\n\n```bash\nexport DEEPSEEK_API_KEY=...\n\nfak serve --provider anthropic \\\n --base-url \"https://api.deepseek.com/anthropic\" \\\n --model \"deepseek-v4-pro\" \\\n --api-key-env DEEPSEEK_API_KEY\n```\n\n### 3. Self-hosted route (your own GPUs)\n\nRun the model on a tuned engine exposing the OpenAI surface, then front it. This\nis the path for a self-hosted bring-up — see [Bring V4 up on a GPU node](#bring-v4-up-on-a-gpu-node)\nfor the one-command script and the **architecture floor** you must confirm first.\n\n```bash\n# On the serving node (see the self-host runbook for engine flags):\nvllm serve deepseek-ai/DeepSeek-V4-Pro --served-model-name deepseek-ai/DeepSeek-V4-Pro --port 8000\n# → OpenAI root: http://:8000/v1\n\n# Then, from anywhere that can reach it:\nfak serve --provider openai-compatible \\\n --base-url \"http://:8000/v1\" \\\n --model \"deepseek-ai/DeepSeek-V4-Pro\"\n```\n\nfak makes **no claim to natively run** the 1.6T-total V4-Pro weights; the model is\nserved by vLLM/SGLang/NIM and fak sits in front. Native in-kernel loader work is a\nseparately scoped effort (parent program\n[#3006](https://github.com/anthony-chaudhary/fak/issues/3006)).\n\n## Thinking-mode caveats (these bite in practice)\n\nDeepSeek V4 is a reasoning model. The behavior differs from a plain chat model in\nways that affect harness integration:\n\n- **`thinking` defaults to ENABLED.** A turn may spend its whole token budget on\n reasoning and return **empty `content` with populated `reasoning_content`** —\n that is a live, correct response, not a failure. (fak's self-host smoke treats a\n reasoning-only turn as a live wire for exactly this reason.)\n- **`reasoning_effort` accepts only `high` / `max`.** There is no low/medium tier.\n- **Sampling controls do not affect thinking mode.** `temperature`, `top_p`,\n `presence_penalty`, and `frequency_penalty` have **no effect** on the thinking\n path — do not expect them to steer reasoning.\n- **Tool-call turns require reasoning-content transcript preservation.** When a\n turn makes a tool call, the `reasoning_content` from that turn must be carried\n forward in the transcript for the follow-up turn to be correct. fak preserves\n DeepSeek reasoning content across tool-call turns (see the gateway/agent\n reasoning-preservation coverage); a harness that strips it will degrade the\n model.\n\n## Cache counters are provider-relayed, not fak-authored savings\n\nDeepSeek reports prompt-cache hit/miss token counts in its usage block. fak\n**relays and prices these as the provider reports them** — they are\n**provider-relayed, provider-priced** counters. They are **not** a fak-authored\nsaving and must never be presented as fak cache savings. fak's own cache economics\n(the addressable in-kernel cache) are a distinct, separately-witnessed subsystem.\n\n## Bring V4 up on a GPU node\n\nThe one-command path is [`scripts/dgx-deepseek-serve.sh`](../../scripts/dgx-deepseek-serve.sh):\nit detects your GPUs, **gates on the DeepSeek-V4 architecture floor**, launches the\nOpenAI server on `:8000`, health-checks `/models`, and prints the exact `fak serve`\n+ wire-witness commands.\n\n> **Architecture floor — confirm before reserving GPU time.** V4 checkpoints are\n> **FP4** (MoE experts) + **FP8** (most params) with an **FP4 attention indexer**,\n> and the attention stack is **DSA (DeepSeek Sparse Attention)**. **Stock** kernels\n> (vLLM/SGLang/NIM) generally need **sm_90+ (Hopper)**, with **sm_100 (Blackwell)**\n> for native NVFP4. `scripts/dgx-deepseek-serve.sh` fronts a stock engine and warns\n> and refuses below the floor unless `FAK_DGX_FORCE=1`. V4-**Pro** (1.6T total) is a\n> **multi-node** expert-parallel deployment; size a single node against V4-**Flash**\n> first.\n>\n> **sm_80 (Ampere/A100) is no longer dead-ended.** A V4-aware **llama.cpp fork**\n> ([cchuter `feat/v4-port-cuda`](https://github.com/cchuter/llama.cpp); upstream\n> [PR #24162](https://github.com/ggml-org/llama.cpp/pull/24162) for Unsloth quants)\n> serves **V4-Flash** on sm_80 via CUDA V4-op kernels + a software-emulated FP8 path\n> — the same route that overcame the sm_90 wall for GLM-5.2 on A100. V4-Flash GGUFs\n> (≈103–162 GB) fit **resident** on 8×A100-80 GB. The one-command harness is\n> [`tools/deepseekv4_stage_serve_gpu_server.sh`](../../tools/deepseekv4_stage_serve_gpu_server.sh).\n> This is the interim serve path; the fak-native V4 kernel is a separate track\n> ([#3016](https://github.com/anthony-chaudhary/fak/issues/3016)–[#3019](https://github.com/anthony-chaudhary/fak/issues/3019)).\n\nThe full procedure, the minimum-evidence witness ladder, and the comparability\nboundary are in the\n[self-host baseline runbook](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md).\n\n### Wire witness (what fak has proven)\n\nfak's `openai-compatible` route has been **witnessed live against a real DeepSeek-V4\nendpoint** (`deepseek-ai/deepseek-v4-flash`), passing all three rungs — readiness,\nnon-streaming (with honest usage counters), and streaming to `[DONE]`. See the\nrunbook's [Live witness](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md#live-witness--wire-proven-on-a-real-v4-endpoint)\nsection. This proves the **wire**; a **performance headline** still requires the\ntuned EP/EPLB baseline on a dedicated node and is not claimed here.\n\n## Budget before you claim 1M context\n\nBefore quoting 1M-context support, model the memory/KV economics with the\nchecked-in `fak`-side budget calculator (issue\n[#3015](https://github.com/anthony-chaudhary/fak/issues/3015),\n`internal/gateway/deepseek_budget.go`): it estimates FP4+FP8 weight storage,\nactivated-parameter compute proxy, and compressed-KV vs SWA/state-cache tail\npressure per model and context length, labelling every row **MODELED** until fed by\nwitnessed serving telemetry.\n\n## Acceptance mapping (issue #3012)\n\n- **Supported-provider page added** — this page.\n- **Copy/paste commands for all three routes** — [above](#the-three-routes-at-a-glance),\n each using `DEEPSEEK_API_KEY`, never a literal key.\n- **Thinking-mode caveats documented** — [Thinking-mode caveats](#thinking-mode-caveats-these-bite-in-practice):\n `thinking` default-on, `reasoning_effort` high/max only, sampling controls inert,\n tool-call reasoning-content preservation.\n- **Provider cache counters framed as provider-relayed/provider-priced** —\n [Cache counters](#cache-counters-are-provider-relayed-not-fak-authored-savings).\n- **Alias-retirement warning prominent** — the top-of-page callout (2026-07-24\n 15:59 UTC).\n- **Links to official sources with the research date** —\n [Sources](#sources-researched-july-2026).\n- **`rg \"deepseek-chat|deepseek-reasoner\" docs examples`** finds only this\n migration/compatibility context.\n\n## Sources (researched July 2026)\n\n- DeepSeek API quick start (OpenAI-compatible base URL, `thinking`,\n `reasoning_effort`, streaming): \n- Coding-agents guide (Anthropic-compatible route for Claude Code/OpenCode):\n \n- Pricing / model page (capabilities, context, max output, alias retirement):\n \n- V4 preview / alias-retirement announcement (`deepseek-chat` / `deepseek-reasoner`\n retire 2026-07-24 15:59 UTC): \n- V4-Pro model card (FP4+FP8 mixed precision, MoE experts FP4):\n \n\n## Cross-links\n\n- [Clouds & hosted providers](clouds.md) — the full hosted-provider list.\n- [Self-host baseline runbook](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md)\n · [Perf scorecard](../benchmarks/DEEPSEEK-V4-PERF-SCORECARD.md)\n- [Model accounts](../model-accounts.md) · Parent program\n [#3006](https://github.com/anthony-chaudhary/fak/issues/3006).\n"}, "9ad1022fc5d4dd8141cd0b9d43bdc27d2557de76": {"t": 1785749568.3825479, "n": 10736, "h": "5e88f18cb5b667e8", "c": "---\ntitle: \"DeepSeek V4 through fak — hosted, Anthropic-compatible, and self-hosted routes\"\ndescription: \"The exact copy/paste commands for the three DeepSeek V4 routes fak fronts (hosted OpenAI-compatible, Anthropic-compatible for Claude Code-style clients, and self-hosted vLLM/SGLang/NIM), the thinking-mode caveats, the provider-relayed cache-counter stance, the self-hosted GPU-node bring-up path, and the prominent warning that the deepseek-chat / deepseek-reasoner aliases retire 2026-07-24 15:59 UTC.\"\n---\n\n# DeepSeek V4 through fak\n\nfak fronts DeepSeek as a wire, the same way it fronts any provider: it **governs\nand relays; it does not generate**. This page is the operator's exact-command\nreference for the three DeepSeek V4 routes, the thinking-mode caveats that bite in\npractice, and the honest limits. It is grounded in the official DeepSeek docs\n(researched **July 2026**, linked in [Sources](#sources-researched-july-2026)).\n\n> ## ⚠️ Alias retirement — `deepseek-chat` / `deepseek-reasoner` retire 2026-07-24 15:59 UTC\n>\n> The `deepseek-chat` and `deepseek-reasoner` model ids are **compatibility\n> aliases only** and retire on **2026-07-24 15:59 UTC**\n> ([vendor announcement](https://api-docs.deepseek.com/news/news260424)). Pin\n> **`deepseek-v4-pro`** (or `deepseek-v4-flash`) explicitly. Any script, account\n> profile, or harness config still naming an alias after the cutoff will break —\n> migrate now.\n\n## The three routes at a glance\n\n| Route | When to use it | Model id | Base URL |\n|---|---|---|---|\n| **Hosted OpenAI-compatible** | Any OpenAI-Chat-Completions client through `fak serve`/`fak guard` | `deepseek-v4-pro` | `https://api.deepseek.com` |\n| **Anthropic-compatible** | Claude Code-style clients that speak Anthropic Messages | `deepseek-v4-pro` | `https://api.deepseek.com/anthropic` |\n| **Self-hosted (vLLM/SGLang/NIM)** | Your own GPUs (Hopper/Blackwell-class node) | `deepseek-ai/DeepSeek-V4-Pro` / `-Flash` | `http://:8000/v1` |\n\nAll examples use the `DEEPSEEK_API_KEY` environment variable and **never a literal\nkey**.\n\n### 1. Hosted OpenAI-compatible route\n\nThe hosted DeepSeek API exposes the OpenAI Chat Completions surface. Front it with\nfak as a generic OpenAI-compatible upstream:\n\n```bash\nexport DEEPSEEK_API_KEY=... # your key; never commit it\n\nfak serve --provider openai-compatible \\\n --base-url \"https://api.deepseek.com\" \\\n --model \"deepseek-v4-pro\" \\\n --api-key-env DEEPSEEK_API_KEY\n```\n\nThe first request uses model `deepseek-v4-pro`, an OpenAI-compatible base URL, and\nsupports `thinking`, `reasoning_effort`, and the streaming toggle\n([vendor quick start](https://api-docs.deepseek.com/)).\n\n### 2. Anthropic-compatible route (Claude Code-style clients)\n\nDeepSeek also exposes an **Anthropic Messages**-compatible surface at\n`https://api.deepseek.com/anthropic`\n([coding-agents guide](https://api-docs.deepseek.com/guides/coding_agents)). A\nClaude Code-style client can point at fak, which fronts that surface:\n\n```bash\nexport DEEPSEEK_API_KEY=...\n\nfak serve --provider anthropic \\\n --base-url \"https://api.deepseek.com/anthropic\" \\\n --model \"deepseek-v4-pro\" \\\n --api-key-env DEEPSEEK_API_KEY\n```\n\n### 3. Self-hosted route (your own GPUs)\n\nRun the model on a tuned engine exposing the OpenAI surface, then front it. This\nis the path for a self-hosted bring-up — see [Bring V4 up on a GPU node](#bring-v4-up-on-a-gpu-node)\nfor the one-command script and the **architecture floor** you must confirm first.\n\n```bash\n# On the serving node (see the self-host runbook for engine flags):\nvllm serve deepseek-ai/DeepSeek-V4-Pro --served-model-name deepseek-ai/DeepSeek-V4-Pro --port 8000\n# → OpenAI root: http://:8000/v1\n\n# Then, from anywhere that can reach it:\nfak serve --provider openai-compatible \\\n --base-url \"http://:8000/v1\" \\\n --model \"deepseek-ai/DeepSeek-V4-Pro\"\n```\n\nfak makes **no claim to natively run** the 1.6T-total V4-Pro weights; the model is\nserved by vLLM/SGLang/NIM and fak sits in front. Native in-kernel loader work is a\nseparately scoped effort (parent program\n[#3006](https://github.com/anthony-chaudhary/fak/issues/3006)).\n\n## Thinking-mode caveats (these bite in practice)\n\nDeepSeek V4 is a reasoning model. The behavior differs from a plain chat model in\nways that affect harness integration:\n\n- **`thinking` defaults to ENABLED.** A turn may spend its whole token budget on\n reasoning and return **empty `content` with populated `reasoning_content`** —\n that is a live, correct response, not a failure. (fak's self-host smoke treats a\n reasoning-only turn as a live wire for exactly this reason.)\n- **`reasoning_effort` accepts only `high` / `max`.** There is no low/medium tier.\n- **Sampling controls do not affect thinking mode.** `temperature`, `top_p`,\n `presence_penalty`, and `frequency_penalty` have **no effect** on the thinking\n path — do not expect them to steer reasoning.\n- **Tool-call turns require reasoning-content transcript preservation.** When a\n turn makes a tool call, the `reasoning_content` from that turn must be carried\n forward in the transcript for the follow-up turn to be correct. fak preserves\n DeepSeek reasoning content across tool-call turns (see the gateway/agent\n reasoning-preservation coverage); a harness that strips it will degrade the\n model.\n\n## Cache counters are provider-relayed, not fak-authored savings\n\nDeepSeek reports prompt-cache hit/miss token counts in its usage block. fak\n**relays and prices these as the provider reports them** — they are\n**provider-relayed, provider-priced** counters. They are **not** a fak-authored\nsaving and must never be presented as fak cache savings. fak's own cache economics\n(the addressable in-kernel cache) are a distinct, separately-witnessed subsystem.\n\n## Bring V4 up on a GPU node\n\nThe one-command path is [`scripts/dgx-deepseek-serve.sh`](../../scripts/dgx-deepseek-serve.sh):\nit detects your GPUs, **gates on the DeepSeek-V4 architecture floor**, launches the\nOpenAI server on `:8000`, health-checks `/models`, and prints the exact `fak serve`\n+ wire-witness commands.\n\n> **Architecture floor — confirm before reserving GPU time.** V4 checkpoints are\n> **FP4** (MoE experts) + **FP8** (most params) with an **FP4 attention indexer**,\n> and the attention stack is **DSA (DeepSeek Sparse Attention)**. **Stock** kernels\n> (vLLM/SGLang/NIM) generally need **sm_90+ (Hopper)**, with **sm_100 (Blackwell)**\n> for native NVFP4. `scripts/dgx-deepseek-serve.sh` fronts a stock engine and warns\n> and refuses below the floor unless `FAK_DGX_FORCE=1`. V4-**Pro** (1.6T total) is a\n> **multi-node** expert-parallel deployment; size a single node against V4-**Flash**\n> first.\n>\n> **sm_80 (Ampere/A100) is no longer dead-ended.** A V4-aware **llama.cpp fork**\n> ([cchuter `feat/v4-port-cuda`](https://github.com/cchuter/llama.cpp); upstream\n> [PR #24162](https://github.com/ggml-org/llama.cpp/pull/24162) for Unsloth quants)\n> serves **V4-Flash** on sm_80 via CUDA V4-op kernels + a software-emulated FP8 path\n> — the same route that overcame the sm_90 wall for GLM-5.2 on A100. V4-Flash GGUFs\n> (≈103–162 GB) fit **resident** on 8×A100-80 GB. The one-command harness is\n> [`tools/deepseekv4_stage_serve_gpu_server.sh`](../../tools/deepseekv4_stage_serve_gpu_server.sh).\n> This is the interim serve path; the fak-native V4 kernel is a separate track\n> ([#3016](https://github.com/anthony-chaudhary/fak/issues/3016)–[#3019](https://github.com/anthony-chaudhary/fak/issues/3019)).\n\nThe full procedure, the minimum-evidence witness ladder, and the comparability\nboundary are in the\n[self-host baseline runbook](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md).\n\n### Wire witness (what fak has proven)\n\nfak's `openai-compatible` route has been **witnessed live against a real DeepSeek-V4\nendpoint** (`deepseek-ai/deepseek-v4-flash`), passing all three rungs — readiness,\nnon-streaming (with honest usage counters), and streaming to `[DONE]`. See the\nrunbook's [Live witness](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md#live-witness--wire-proven-on-a-real-v4-endpoint)\nsection. This proves the **wire**; a **performance headline** still requires the\ntuned EP/EPLB baseline on a dedicated node and is not claimed here.\n\n## Budget before you claim 1M context\n\nBefore quoting 1M-context support, model the memory/KV economics with the\nchecked-in `fak`-side budget calculator (issue\n[#3015](https://github.com/anthony-chaudhary/fak/issues/3015),\n`internal/gateway/deepseek_budget.go`): it estimates FP4+FP8 weight storage,\nactivated-parameter compute proxy, and compressed-KV vs SWA/state-cache tail\npressure per model and context length, labelling every row **MODELED** until fed by\nwitnessed serving telemetry.\n\n## Acceptance mapping (issue #3012)\n\n- **Supported-provider page added** — this page.\n- **Copy/paste commands for all three routes** — [above](#the-three-routes-at-a-glance),\n each using `DEEPSEEK_API_KEY`, never a literal key.\n- **Thinking-mode caveats documented** — [Thinking-mode caveats](#thinking-mode-caveats-these-bite-in-practice):\n `thinking` default-on, `reasoning_effort` high/max only, sampling controls inert,\n tool-call reasoning-content preservation.\n- **Provider cache counters framed as provider-relayed/provider-priced** —\n [Cache counters](#cache-counters-are-provider-relayed-not-fak-authored-savings).\n- **Alias-retirement warning prominent** — the top-of-page callout (2026-07-24\n 15:59 UTC).\n- **Links to official sources with the research date** —\n [Sources](#sources-researched-july-2026).\n- **`rg \"deepseek-chat|deepseek-reasoner\" docs examples`** finds only this\n migration/compatibility context.\n\n## Sources (researched July 2026)\n\n- DeepSeek API quick start (OpenAI-compatible base URL, `thinking`,\n `reasoning_effort`, streaming): \n- Coding-agents guide (Anthropic-compatible route for Claude Code/OpenCode):\n \n- Pricing / model page (capabilities, context, max output, alias retirement):\n \n- V4 preview / alias-retirement announcement (`deepseek-chat` / `deepseek-reasoner`\n retire 2026-07-24 15:59 UTC): \n- V4-Pro model card (FP4+FP8 mixed precision, MoE experts FP4):\n \n\n## Cross-links\n\n- [Clouds & hosted providers](clouds.md) — the full hosted-provider list.\n- [Self-host baseline runbook](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md)\n · [Perf scorecard](../benchmarks/DEEPSEEK-V4-PERF-SCORECARD.md)\n- [Model accounts](../model-accounts.md) · Parent program\n [#3006](https://github.com/anthony-chaudhary/fak/issues/3006).\n"}, "a371a24688c1ecb9933731df766396e901bdab60": {"t": 1785749569.7757096, "n": 10724, "h": "ff7ae63289a725da", "c": "---\ntitle: \"DeepSeek V4 through fak — hosted, Anthropic-compatible, and self-hosted routes\"\ndescription: \"The exact copy/paste commands for the three DeepSeek V4 routes fak fronts (hosted OpenAI-compatible, Anthropic-compatible for Claude Code-style clients, and self-hosted vLLM/SGLang/NIM), the thinking-mode caveats, the provider-relayed cache-counter stance, the self-hosted GPU-node bring-up path, and the prominent warning that the deepseek-chat / deepseek-reasoner aliases retire 2026-07-24 15:59 UTC.\"\n---\n\n# DeepSeek V4 through fak\n\nfak fronts DeepSeek as a wire, the same way it fronts any provider: it **governs\nand relays; it does not generate**. This page is the operator's exact-command\nreference for the three DeepSeek V4 routes, the thinking-mode caveats that bite in\npractice, and the honest limits. It is grounded in the official DeepSeek docs\n(researched **July 2026**, linked in [Sources](#sources-researched-july-2026)).\n\n> ## ⚠️ Alias retirement — `deepseek-chat` / `deepseek-reasoner` retire 2026-07-24 15:59 UTC\n>\n> The `deepseek-chat` and `deepseek-reasoner` model ids are **compatibility\n> aliases only** and retire on **2026-07-24 15:59 UTC**\n> ([vendor announcement](https://api-docs.deepseek.com/news/news260424)). Pin\n> **`deepseek-v4-pro`** (or `deepseek-v4-flash`) explicitly. Any script, account\n> profile, or harness config still naming an alias after the cutoff will break —\n> migrate now.\n\n## The three routes at a glance\n\n| Route | When to use it | Model id | Base URL |\n|---|---|---|---|\n| **Hosted OpenAI-compatible** | Any OpenAI-Chat-Completions client through `fak serve`/`fak guard` | `deepseek-v4-pro` | `https://api.deepseek.com` |\n| **Anthropic-compatible** | Claude Code-style clients that speak Anthropic Messages | `deepseek-v4-pro` | `https://api.deepseek.com/anthropic` |\n| **Self-hosted (vLLM/SGLang/NIM)** | Your own GPUs (Hopper/Blackwell-class node) | `deepseek-ai/DeepSeek-V4-Pro` / `-Flash` | `http://:8000/v1` |\n\nAll examples use the `DEEPSEEK_API_KEY` environment variable and **never a literal\nkey**.\n\n### 1. Hosted OpenAI-compatible route\n\nThe hosted DeepSeek API exposes the OpenAI Chat Completions surface. Front it with\nfak as a generic OpenAI-compatible upstream:\n\n```bash\nexport DEEPSEEK_API_KEY=... # your key; never commit it\n\nfak serve --provider openai-compatible \\\n --base-url \"https://api.deepseek.com\" \\\n --model \"deepseek-v4-pro\" \\\n --api-key-env DEEPSEEK_API_KEY\n```\n\nThe first request uses model `deepseek-v4-pro`, an OpenAI-compatible base URL, and\nsupports `thinking`, `reasoning_effort`, and the streaming toggle\n([vendor quick start](https://api-docs.deepseek.com/)).\n\n### 2. Anthropic-compatible route (Claude Code-style clients)\n\nDeepSeek also exposes an **Anthropic Messages**-compatible surface at\n`https://api.deepseek.com/anthropic`\n([coding-agents guide](https://api-docs.deepseek.com/guides/coding_agents)). A\nClaude Code-style client can point at fak, which fronts that surface:\n\n```bash\nexport DEEPSEEK_API_KEY=...\n\nfak serve --provider anthropic \\\n --base-url \"https://api.deepseek.com/anthropic\" \\\n --model \"deepseek-v4-pro\" \\\n --api-key-env DEEPSEEK_API_KEY\n```\n\n### 3. Self-hosted route (your own GPUs)\n\nRun the model on a tuned engine exposing the OpenAI surface, then front it. This\nis the path for a self-hosted bring-up — see [Bring V4 up on a GPU node](#bring-v4-up-on-a-gpu-node)\nfor the turnkey script and the **architecture floor** you must confirm first.\n\n```bash\n# On the serving node (see the self-host runbook for engine flags):\nvllm serve deepseek-ai/DeepSeek-V4-Pro --served-model-name deepseek-ai/DeepSeek-V4-Pro --port 8000\n# → OpenAI root: http://:8000/v1\n\n# Then, from anywhere that can reach it:\nfak serve --provider openai-compatible \\\n --base-url \"http://:8000/v1\" \\\n --model \"deepseek-ai/DeepSeek-V4-Pro\"\n```\n\nfak makes **no claim to natively run** the 1.6T-total V4-Pro weights; the model is\nserved by vLLM/SGLang/NIM and fak sits in front. Native in-kernel loader work is a\nseparately scoped effort (parent program\n[#3006](https://github.com/anthony-chaudhary/fak/issues/3006)).\n\n## Thinking-mode caveats (these bite in practice)\n\nDeepSeek V4 is a reasoning model. The behavior differs from a plain chat model in\nways that affect harness integration:\n\n- **`thinking` defaults to ENABLED.** A turn may spend its whole token budget on\n reasoning and return **empty `content` with populated `reasoning_content`** —\n that is a live, correct response, not a failure. (fak's self-host smoke treats a\n reasoning-only turn as a live wire for exactly this reason.)\n- **`reasoning_effort` accepts only `high` / `max`.** There is no low/medium tier.\n- **Sampling controls do not affect thinking mode.** `temperature`, `top_p`,\n `presence_penalty`, and `frequency_penalty` have **no effect** on the thinking\n path — do not expect them to steer reasoning.\n- **Tool-call turns require reasoning-content transcript preservation.** When a\n turn makes a tool call, the `reasoning_content` from that turn must be carried\n forward in the transcript for the follow-up turn to be correct. fak preserves\n DeepSeek reasoning content across tool-call turns (see the gateway/agent\n reasoning-preservation coverage); a harness that strips it will degrade the\n model.\n\n## Cache counters are provider-relayed, not fak-authored savings\n\nDeepSeek reports prompt-cache hit/miss token counts in its usage block. fak\n**relays and prices these as the provider reports them** — they are\n**provider-relayed, provider-priced** counters. They are **not** a fak-authored\nsaving and must never be presented as fak cache savings. fak's own cache economics\n(the addressable in-kernel cache) are a distinct, separately-witnessed subsystem.\n\n## Bring V4 up on a GPU node\n\nThe turnkey path is [`scripts/dgx-deepseek-serve.sh`](../../scripts/dgx-deepseek-serve.sh):\nit detects your GPUs, **gates on the DeepSeek-V4 architecture floor**, launches the\nOpenAI server on `:8000`, health-checks `/models`, and prints the exact `fak serve`\n+ wire-witness commands.\n\n> **Architecture floor — confirm before reserving GPU time.** V4 checkpoints are\n> **FP4** (MoE experts) + **FP8** (most params) with an **FP4 attention indexer**,\n> and the attention stack is **DSA (DeepSeek Sparse Attention)**. **Stock** kernels\n> (vLLM/SGLang/NIM) generally need **sm_90+ (Hopper)**, with **sm_100 (Blackwell)**\n> for native NVFP4. `scripts/dgx-deepseek-serve.sh` fronts a stock engine and warns\n> and refuses below the floor unless `FAK_DGX_FORCE=1`. V4-**Pro** (1.6T total) is a\n> **multi-node** expert-parallel deployment; size a single node against V4-**Flash**\n> first.\n>\n> **sm_80 (Ampere/A100) is no longer dead-ended.** A V4-aware **llama.cpp fork**\n> ([cchuter `feat/v4-port-cuda`](https://github.com/cchuter/llama.cpp); upstream\n> [PR #24162](https://github.com/ggml-org/llama.cpp/pull/24162) for Unsloth quants)\n> serves **V4-Flash** on sm_80 via CUDA V4-op kernels + a software-emulated FP8 path\n> — the same route that overcame the sm_90 wall for GLM-5.2 on A100. V4-Flash GGUFs\n> (≈103–162 GB) fit **resident** on 8×A100-80 GB. The turnkey harness is\n> [`tools/deepseekv4_stage_serve_gpu_server.sh`](../../tools/deepseekv4_stage_serve_gpu_server.sh).\n> This is the interim serve path; the fak-native V4 kernel is a separate track\n> ([#3016](https://github.com/anthony-chaudhary/fak/issues/3016)–[#3019](https://github.com/anthony-chaudhary/fak/issues/3019)).\n\nThe full procedure, the minimum-evidence witness ladder, and the comparability\nboundary are in the\n[self-host baseline runbook](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md).\n\n### Wire witness (what fak has proven)\n\nfak's `openai-compatible` route has been **witnessed live against a real DeepSeek-V4\nendpoint** (`deepseek-ai/deepseek-v4-flash`), passing all three rungs — readiness,\nnon-streaming (with honest usage counters), and streaming to `[DONE]`. See the\nrunbook's [Live witness](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md#live-witness--wire-proven-on-a-real-v4-endpoint)\nsection. This proves the **wire**; a **performance headline** still requires the\ntuned EP/EPLB baseline on a dedicated node and is not claimed here.\n\n## Budget before you claim 1M context\n\nBefore quoting 1M-context support, model the memory/KV economics with the\nchecked-in `fak`-side budget calculator (issue\n[#3015](https://github.com/anthony-chaudhary/fak/issues/3015),\n`internal/gateway/deepseek_budget.go`): it estimates FP4+FP8 weight storage,\nactivated-parameter compute proxy, and compressed-KV vs SWA/state-cache tail\npressure per model and context length, labelling every row **MODELED** until fed by\nwitnessed serving telemetry.\n\n## Acceptance mapping (issue #3012)\n\n- **Supported-provider page added** — this page.\n- **Copy/paste commands for all three routes** — [above](#the-three-routes-at-a-glance),\n each using `DEEPSEEK_API_KEY`, never a literal key.\n- **Thinking-mode caveats documented** — [Thinking-mode caveats](#thinking-mode-caveats-these-bite-in-practice):\n `thinking` default-on, `reasoning_effort` high/max only, sampling controls inert,\n tool-call reasoning-content preservation.\n- **Provider cache counters framed as provider-relayed/provider-priced** —\n [Cache counters](#cache-counters-are-provider-relayed-not-fak-authored-savings).\n- **Alias-retirement warning prominent** — the top-of-page callout (2026-07-24\n 15:59 UTC).\n- **Links to official sources with the research date** —\n [Sources](#sources-researched-july-2026).\n- **`rg \"deepseek-chat|deepseek-reasoner\" docs examples`** finds only this\n migration/compatibility context.\n\n## Sources (researched July 2026)\n\n- DeepSeek API quick start (OpenAI-compatible base URL, `thinking`,\n `reasoning_effort`, streaming): \n- Coding-agents guide (Anthropic-compatible route for Claude Code/OpenCode):\n \n- Pricing / model page (capabilities, context, max output, alias retirement):\n \n- V4 preview / alias-retirement announcement (`deepseek-chat` / `deepseek-reasoner`\n retire 2026-07-24 15:59 UTC): \n- V4-Pro model card (FP4+FP8 mixed precision, MoE experts FP4):\n \n\n## Cross-links\n\n- [Clouds & hosted providers](clouds.md) — the full hosted-provider list.\n- [Self-host baseline runbook](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md)\n · [Perf scorecard](../benchmarks/DEEPSEEK-V4-PERF-SCORECARD.md)\n- [Model accounts](../model-accounts.md) · Parent program\n [#3006](https://github.com/anthony-chaudhary/fak/issues/3006).\n"}, "9ad773687ec90784a89d68633786a28a415cd76e": {"t": 1785749570.8810918, "n": 10712, "h": "2fa11d70f9818a69", "c": "---\ntitle: \"DeepSeek V4 through fak — hosted, Anthropic-compatible, and self-hosted routes\"\ndescription: \"The exact copy/paste commands for the three DeepSeek V4 routes fak fronts (hosted OpenAI-compatible, Anthropic-compatible for Claude Code-style clients, and self-hosted vLLM/SGLang/NIM), the thinking-mode caveats, the provider-relayed cache-counter stance, the self-hosted GPU-node bring-up path, and the prominent warning that the deepseek-chat / deepseek-reasoner aliases retire 2026-07-24 15:59 UTC.\"\n---\n\n# DeepSeek V4 through fak\n\nfak fronts DeepSeek as a wire, the same way it fronts any provider: it **governs\nand relays; it does not generate**. This page is the operator's exact-command\nreference for the three DeepSeek V4 routes, the thinking-mode caveats that bite in\npractice, and the honest limits. It is grounded in the official DeepSeek docs\n(researched **July 2026**, linked in [Sources](#sources-researched-july-2026)).\n\n> ## ⚠️ Alias retirement — `deepseek-chat` / `deepseek-reasoner` retire 2026-07-24 15:59 UTC\n>\n> The `deepseek-chat` and `deepseek-reasoner` model ids are **compatibility\n> aliases only** and retire on **2026-07-24 15:59 UTC**\n> ([vendor announcement](https://api-docs.deepseek.com/news/news260424)). Pin\n> **`deepseek-v4-pro`** (or `deepseek-v4-flash`) explicitly. Any script, account\n> profile, or harness config still naming an alias after the cutoff will break —\n> migrate now.\n\n## The three routes at a glance\n\n| Route | When to use it | Model id | Base URL |\n|---|---|---|---|\n| **Hosted OpenAI-compatible** | Any OpenAI-Chat-Completions client through `fak serve`/`fak guard` | `deepseek-v4-pro` | `https://api.deepseek.com` |\n| **Anthropic-compatible** | Claude Code-style clients that speak Anthropic Messages | `deepseek-v4-pro` | `https://api.deepseek.com/anthropic` |\n| **Self-hosted (vLLM/SGLang/NIM)** | Your own GPUs (Hopper/Blackwell-class node) | `deepseek-ai/DeepSeek-V4-Pro` / `-Flash` | `http://:8000/v1` |\n\nAll examples use the `DEEPSEEK_API_KEY` environment variable and **never a literal\nkey**.\n\n### 1. Hosted OpenAI-compatible route\n\nThe hosted DeepSeek API exposes the OpenAI Chat Completions surface. Front it with\nfak as a generic OpenAI-compatible upstream:\n\n```bash\nexport DEEPSEEK_API_KEY=... # your key; never commit it\n\nfak serve --provider openai-compatible \\\n --base-url \"https://api.deepseek.com\" \\\n --model \"deepseek-v4-pro\" \\\n --api-key-env DEEPSEEK_API_KEY\n```\n\nThe first request uses model `deepseek-v4-pro`, an OpenAI-compatible base URL, and\nsupports `thinking`, `reasoning_effort`, and the streaming toggle\n([vendor quick start](https://api-docs.deepseek.com/)).\n\n### 2. Anthropic-compatible route (Claude Code-style clients)\n\nDeepSeek also exposes an **Anthropic Messages**-compatible surface at\n`https://api.deepseek.com/anthropic`\n([coding-agents guide](https://api-docs.deepseek.com/guides/coding_agents)). A\nClaude Code-style client can point at fak, which fronts that surface:\n\n```bash\nexport DEEPSEEK_API_KEY=...\n\nfak serve --provider anthropic \\\n --base-url \"https://api.deepseek.com/anthropic\" \\\n --model \"deepseek-v4-pro\" \\\n --api-key-env DEEPSEEK_API_KEY\n```\n\n### 3. Self-hosted route (your own GPUs)\n\nRun the model on a tuned engine exposing the OpenAI surface, then front it. This\nis the path for a self-hosted bring-up — see [Bring V4 up on a GPU node](#bring-v4-up-on-a-gpu-node)\nfor the turnkey script and the **architecture floor** you must confirm first.\n\n```bash\n# On the serving node (see the self-host runbook for engine flags):\nvllm serve deepseek-ai/DeepSeek-V4-Pro --served-model-name deepseek-ai/DeepSeek-V4-Pro --port 8000\n# → OpenAI root: http://:8000/v1\n\n# Then, from anywhere that can reach it:\nfak serve --provider openai-compatible \\\n --base-url \"http://:8000/v1\" \\\n --model \"deepseek-ai/DeepSeek-V4-Pro\"\n```\n\nfak makes **no claim to natively run** the 1.6T-total V4-Pro weights; the model is\nserved by vLLM/SGLang/NIM and fak sits in front. Native in-kernel loader work is a\nseparately scoped effort (parent program\n[#3006](https://github.com/anthony-chaudhary/fak/issues/3006)).\n\n## Thinking-mode caveats (these bite in practice)\n\nDeepSeek V4 is a reasoning model. The behavior differs from a plain chat model in\nways that affect harness integration:\n\n- **`thinking` defaults to ENABLED.** A turn may spend its whole token budget on\n reasoning and return **empty `content` with populated `reasoning_content`** —\n that is a live, correct response, not a failure. (fak's self-host smoke treats a\n reasoning-only turn as a live wire for exactly this reason.)\n- **`reasoning_effort` accepts only `high` / `max`.** There is no low/medium tier.\n- **Sampling controls do not affect thinking mode.** `temperature`, `top_p`,\n `presence_penalty`, and `frequency_penalty` have **no effect** on the thinking\n path — do not expect them to steer reasoning.\n- **Tool-call turns require reasoning-content transcript preservation.** When a\n turn makes a tool call, the `reasoning_content` from that turn must be carried\n forward in the transcript for the follow-up turn to be correct. fak preserves\n DeepSeek reasoning content across tool-call turns (see the gateway/agent\n reasoning-preservation coverage); a harness that strips it will degrade the\n model.\n\n## Cache counters are provider-relayed, not fak-authored savings\n\nDeepSeek reports prompt-cache hit/miss token counts in its usage block. fak\n**relays and prices these as the provider reports them** — they are\n**provider-relayed, provider-priced** counters. They are **not** a fak-authored\nsaving and must never be presented as fak cache savings. fak's own cache economics\n(the addressable in-kernel cache) are a distinct, separately-witnessed subsystem.\n\n## Bring V4 up on a GPU node\n\nThe turnkey path is [`scripts/dgx-deepseek-serve.sh`](../../scripts/dgx-deepseek-serve.sh):\nit detects your GPUs, **gates on the DeepSeek-V4 architecture floor**, launches the\nOpenAI server on `:8000`, health-checks `/models`, and prints the exact `fak serve`\n+ wire-witness commands.\n\n> **Architecture floor — confirm before reserving GPU time.** V4 checkpoints are\n> **FP4** (MoE experts) + **FP8** (most params) with an **FP4 attention indexer**,\n> and the attention stack is **DSA (DeepSeek Sparse Attention)**. **Stock** kernels\n> (vLLM/SGLang/NIM) generally need **sm_90+ (Hopper)**, with **sm_100 (Blackwell)**\n> for native NVFP4. `scripts/dgx-deepseek-serve.sh` fronts a stock engine and warns\n> and refuses below the floor unless `FAK_DGX_FORCE=1`. V4-**Pro** (1.6T total) is a\n> **multi-node** expert-parallel deployment; size a single node against V4-**Flash**\n> first.\n>\n> **sm_80 (Ampere/A100) is no longer dead-ended.** A V4-aware **llama.cpp fork**\n> ([cchuter `feat/v4-port-cuda`](https://github.com/cchuter/llama.cpp); upstream\n> [PR #24162](https://github.com/ggml-org/llama.cpp/pull/24162) for Unsloth quants)\n> serves **V4-Flash** on sm_80 via CUDA V4-op kernels + a software-emulated FP8 path\n> — the same route that overcame the sm_90 wall for GLM-5.2 on A100. V4-Flash GGUFs\n> (≈103–162 GB) fit **resident** on 8×A100-80 GB. The turnkey harness is\n> [`tools/deepseekv4_stage_serve_dgx3.sh`](../../tools/deepseekv4_stage_serve_dgx3.sh).\n> This is the interim serve path; the fak-native V4 kernel is a separate track\n> ([#3016](https://github.com/anthony-chaudhary/fak/issues/3016)–[#3019](https://github.com/anthony-chaudhary/fak/issues/3019)).\n\nThe full procedure, the minimum-evidence witness ladder, and the comparability\nboundary are in the\n[self-host baseline runbook](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md).\n\n### Wire witness (what fak has proven)\n\nfak's `openai-compatible` route has been **witnessed live against a real DeepSeek-V4\nendpoint** (`deepseek-ai/deepseek-v4-flash`), passing all three rungs — readiness,\nnon-streaming (with honest usage counters), and streaming to `[DONE]`. See the\nrunbook's [Live witness](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md#live-witness--wire-proven-on-a-real-v4-endpoint)\nsection. This proves the **wire**; a **performance headline** still requires the\ntuned EP/EPLB baseline on a dedicated node and is not claimed here.\n\n## Budget before you claim 1M context\n\nBefore quoting 1M-context support, model the memory/KV economics with the\nchecked-in `fak`-side budget calculator (issue\n[#3015](https://github.com/anthony-chaudhary/fak/issues/3015),\n`internal/gateway/deepseek_budget.go`): it estimates FP4+FP8 weight storage,\nactivated-parameter compute proxy, and compressed-KV vs SWA/state-cache tail\npressure per model and context length, labelling every row **MODELED** until fed by\nwitnessed serving telemetry.\n\n## Acceptance mapping (issue #3012)\n\n- **Supported-provider page added** — this page.\n- **Copy/paste commands for all three routes** — [above](#the-three-routes-at-a-glance),\n each using `DEEPSEEK_API_KEY`, never a literal key.\n- **Thinking-mode caveats documented** — [Thinking-mode caveats](#thinking-mode-caveats-these-bite-in-practice):\n `thinking` default-on, `reasoning_effort` high/max only, sampling controls inert,\n tool-call reasoning-content preservation.\n- **Provider cache counters framed as provider-relayed/provider-priced** —\n [Cache counters](#cache-counters-are-provider-relayed-not-fak-authored-savings).\n- **Alias-retirement warning prominent** — the top-of-page callout (2026-07-24\n 15:59 UTC).\n- **Links to official sources with the research date** —\n [Sources](#sources-researched-july-2026).\n- **`rg \"deepseek-chat|deepseek-reasoner\" docs examples`** finds only this\n migration/compatibility context.\n\n## Sources (researched July 2026)\n\n- DeepSeek API quick start (OpenAI-compatible base URL, `thinking`,\n `reasoning_effort`, streaming): \n- Coding-agents guide (Anthropic-compatible route for Claude Code/OpenCode):\n \n- Pricing / model page (capabilities, context, max output, alias retirement):\n \n- V4 preview / alias-retirement announcement (`deepseek-chat` / `deepseek-reasoner`\n retire 2026-07-24 15:59 UTC): \n- V4-Pro model card (FP4+FP8 mixed precision, MoE experts FP4):\n \n\n## Cross-links\n\n- [Clouds & hosted providers](clouds.md) — the full hosted-provider list.\n- [Self-host baseline runbook](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md)\n · [Perf scorecard](../benchmarks/DEEPSEEK-V4-PERF-SCORECARD.md)\n- [Model accounts](../model-accounts.md) · Parent program\n [#3006](https://github.com/anthony-chaudhary/fak/issues/3006).\n"}, "8785e8b7d4a78959effa1e1d83d454be3b563656": {"t": 1785749572.0750377, "n": 9974, "h": "5d24e9cb7b1cecfb", "c": "---\ntitle: \"DeepSeek V4 through fak — hosted, Anthropic-compatible, and self-hosted routes\"\ndescription: \"The exact copy/paste commands for the three DeepSeek V4 routes fak fronts (hosted OpenAI-compatible, Anthropic-compatible for Claude Code-style clients, and self-hosted vLLM/SGLang/NIM), the thinking-mode caveats, the provider-relayed cache-counter stance, the self-hosted GPU-node bring-up path, and the prominent warning that the deepseek-chat / deepseek-reasoner aliases retire 2026-07-24 15:59 UTC.\"\n---\n\n# DeepSeek V4 through fak\n\nfak fronts DeepSeek as a wire, the same way it fronts any provider: it **governs\nand relays; it does not generate**. This page is the operator's exact-command\nreference for the three DeepSeek V4 routes, the thinking-mode caveats that bite in\npractice, and the honest limits. It is grounded in the official DeepSeek docs\n(researched **July 2026**, linked in [Sources](#sources-researched-july-2026)).\n\n> ## ⚠️ Alias retirement — `deepseek-chat` / `deepseek-reasoner` retire 2026-07-24 15:59 UTC\n>\n> The `deepseek-chat` and `deepseek-reasoner` model ids are **compatibility\n> aliases only** and retire on **2026-07-24 15:59 UTC**\n> ([vendor announcement](https://api-docs.deepseek.com/news/news260424)). Pin\n> **`deepseek-v4-pro`** (or `deepseek-v4-flash`) explicitly. Any script, account\n> profile, or harness config still naming an alias after the cutoff will break —\n> migrate now.\n\n## The three routes at a glance\n\n| Route | When to use it | Model id | Base URL |\n|---|---|---|---|\n| **Hosted OpenAI-compatible** | Any OpenAI-Chat-Completions client through `fak serve`/`fak guard` | `deepseek-v4-pro` | `https://api.deepseek.com` |\n| **Anthropic-compatible** | Claude Code-style clients that speak Anthropic Messages | `deepseek-v4-pro` | `https://api.deepseek.com/anthropic` |\n| **Self-hosted (vLLM/SGLang/NIM)** | Your own GPUs (Hopper/Blackwell-class node) | `deepseek-ai/DeepSeek-V4-Pro` / `-Flash` | `http://:8000/v1` |\n\nAll examples use the `DEEPSEEK_API_KEY` environment variable and **never a literal\nkey**.\n\n### 1. Hosted OpenAI-compatible route\n\nThe hosted DeepSeek API exposes the OpenAI Chat Completions surface. Front it with\nfak as a generic OpenAI-compatible upstream:\n\n```bash\nexport DEEPSEEK_API_KEY=... # your key; never commit it\n\nfak serve --provider openai-compatible \\\n --base-url \"https://api.deepseek.com\" \\\n --model \"deepseek-v4-pro\" \\\n --api-key-env DEEPSEEK_API_KEY\n```\n\nThe first request uses model `deepseek-v4-pro`, an OpenAI-compatible base URL, and\nsupports `thinking`, `reasoning_effort`, and the streaming toggle\n([vendor quick start](https://api-docs.deepseek.com/)).\n\n### 2. Anthropic-compatible route (Claude Code-style clients)\n\nDeepSeek also exposes an **Anthropic Messages**-compatible surface at\n`https://api.deepseek.com/anthropic`\n([coding-agents guide](https://api-docs.deepseek.com/guides/coding_agents)). A\nClaude Code-style client can point at fak, which fronts that surface:\n\n```bash\nexport DEEPSEEK_API_KEY=...\n\nfak serve --provider anthropic \\\n --base-url \"https://api.deepseek.com/anthropic\" \\\n --model \"deepseek-v4-pro\" \\\n --api-key-env DEEPSEEK_API_KEY\n```\n\n### 3. Self-hosted route (your own GPUs)\n\nRun the model on a tuned engine exposing the OpenAI surface, then front it. This\nis the path for a self-hosted bring-up — see [Bring V4 up on a GPU node](#bring-v4-up-on-a-gpu-node)\nfor the turnkey script and the **architecture floor** you must confirm first.\n\n```bash\n# On the serving node (see the self-host runbook for engine flags):\nvllm serve deepseek-ai/DeepSeek-V4-Pro --served-model-name deepseek-ai/DeepSeek-V4-Pro --port 8000\n# → OpenAI root: http://:8000/v1\n\n# Then, from anywhere that can reach it:\nfak serve --provider openai-compatible \\\n --base-url \"http://:8000/v1\" \\\n --model \"deepseek-ai/DeepSeek-V4-Pro\"\n```\n\nfak makes **no claim to natively run** the 1.6T-total V4-Pro weights; the model is\nserved by vLLM/SGLang/NIM and fak sits in front. Native in-kernel loader work is a\nseparately scoped effort (parent program\n[#3006](https://github.com/anthony-chaudhary/fak/issues/3006)).\n\n## Thinking-mode caveats (these bite in practice)\n\nDeepSeek V4 is a reasoning model. The behavior differs from a plain chat model in\nways that affect harness integration:\n\n- **`thinking` defaults to ENABLED.** A turn may spend its whole token budget on\n reasoning and return **empty `content` with populated `reasoning_content`** —\n that is a live, correct response, not a failure. (fak's self-host smoke treats a\n reasoning-only turn as a live wire for exactly this reason.)\n- **`reasoning_effort` accepts only `high` / `max`.** There is no low/medium tier.\n- **Sampling controls do not affect thinking mode.** `temperature`, `top_p`,\n `presence_penalty`, and `frequency_penalty` have **no effect** on the thinking\n path — do not expect them to steer reasoning.\n- **Tool-call turns require reasoning-content transcript preservation.** When a\n turn makes a tool call, the `reasoning_content` from that turn must be carried\n forward in the transcript for the follow-up turn to be correct. fak preserves\n DeepSeek reasoning content across tool-call turns (see the gateway/agent\n reasoning-preservation coverage); a harness that strips it will degrade the\n model.\n\n## Cache counters are provider-relayed, not fak-authored savings\n\nDeepSeek reports prompt-cache hit/miss token counts in its usage block. fak\n**relays and prices these as the provider reports them** — they are\n**provider-relayed, provider-priced** counters. They are **not** a fak-authored\nsaving and must never be presented as fak cache savings. fak's own cache economics\n(the addressable in-kernel cache) are a distinct, separately-witnessed subsystem.\n\n## Bring V4 up on a GPU node\n\nThe turnkey path is [`scripts/dgx-deepseek-serve.sh`](../../scripts/dgx-deepseek-serve.sh):\nit detects your GPUs, **gates on the DeepSeek-V4 architecture floor**, launches the\nOpenAI server on `:8000`, health-checks `/models`, and prints the exact `fak serve`\n+ wire-witness commands.\n\n> **Architecture floor — confirm before reserving GPU time.** V4 checkpoints are\n> **FP4** (MoE experts) + **FP8** (most params) with an **FP4 attention indexer**,\n> and the attention stack is **DSA (DeepSeek Sparse Attention)**. Stock kernels\n> generally need **sm_90+ (Hopper)**, with **sm_100 (Blackwell)** for native NVFP4.\n> A **Volta-class node (V100, sm_70) cannot serve a stock V4 path**; an Ampere\n> sm_80 node is BF16-dense-fallback-only. The script warns and refuses below the\n> floor unless `FAK_DGX_FORCE=1`. V4-**Pro** (1.6T total) is a **multi-node**\n> expert-parallel deployment; size a single node against V4-**Flash** first.\n\nThe full procedure, the minimum-evidence witness ladder, and the comparability\nboundary are in the\n[self-host baseline runbook](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md).\n\n### Wire witness (what fak has proven)\n\nfak's `openai-compatible` route has been **witnessed live against a real DeepSeek-V4\nendpoint** (`deepseek-ai/deepseek-v4-flash`), passing all three rungs — readiness,\nnon-streaming (with honest usage counters), and streaming to `[DONE]`. See the\nrunbook's [Live witness](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md#live-witness--wire-proven-on-a-real-v4-endpoint)\nsection. This proves the **wire**; a **performance headline** still requires the\ntuned EP/EPLB baseline on a dedicated node and is not claimed here.\n\n## Budget before you claim 1M context\n\nBefore quoting 1M-context support, model the memory/KV economics with the\nchecked-in `fak`-side budget calculator (issue\n[#3015](https://github.com/anthony-chaudhary/fak/issues/3015),\n`internal/gateway/deepseek_budget.go`): it estimates FP4+FP8 weight storage,\nactivated-parameter compute proxy, and compressed-KV vs SWA/state-cache tail\npressure per model and context length, labelling every row **MODELED** until fed by\nwitnessed serving telemetry.\n\n## Acceptance mapping (issue #3012)\n\n- **Supported-provider page added** — this page.\n- **Copy/paste commands for all three routes** — [above](#the-three-routes-at-a-glance),\n each using `DEEPSEEK_API_KEY`, never a literal key.\n- **Thinking-mode caveats documented** — [Thinking-mode caveats](#thinking-mode-caveats-these-bite-in-practice):\n `thinking` default-on, `reasoning_effort` high/max only, sampling controls inert,\n tool-call reasoning-content preservation.\n- **Provider cache counters framed as provider-relayed/provider-priced** —\n [Cache counters](#cache-counters-are-provider-relayed-not-fak-authored-savings).\n- **Alias-retirement warning prominent** — the top-of-page callout (2026-07-24\n 15:59 UTC).\n- **Links to official sources with the research date** —\n [Sources](#sources-researched-july-2026).\n- **`rg \"deepseek-chat|deepseek-reasoner\" docs examples`** finds only this\n migration/compatibility context.\n\n## Sources (researched July 2026)\n\n- DeepSeek API quick start (OpenAI-compatible base URL, `thinking`,\n `reasoning_effort`, streaming): \n- Coding-agents guide (Anthropic-compatible route for Claude Code/OpenCode):\n \n- Pricing / model page (capabilities, context, max output, alias retirement):\n \n- V4 preview / alias-retirement announcement (`deepseek-chat` / `deepseek-reasoner`\n retire 2026-07-24 15:59 UTC): \n- V4-Pro model card (FP4+FP8 mixed precision, MoE experts FP4):\n \n\n## Cross-links\n\n- [Clouds & hosted providers](clouds.md) — the full hosted-provider list.\n- [Self-host baseline runbook](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md)\n · [Perf scorecard](../benchmarks/DEEPSEEK-V4-PERF-SCORECARD.md)\n- [Model accounts](../model-accounts.md) · Parent program\n [#3006](https://github.com/anthony-chaudhary/fak/issues/3006).\n"}, "13f5f1a6e0391eb28bcbea759890fb009251e5d1": {"t": 1786067794.6565115, "n": 10736, "h": "5e88f18cb5b667e8", "c": "---\ntitle: \"DeepSeek V4 through fak — hosted, Anthropic-compatible, and self-hosted routes\"\ndescription: \"The exact copy/paste commands for the three DeepSeek V4 routes fak fronts (hosted OpenAI-compatible, Anthropic-compatible for Claude Code-style clients, and self-hosted vLLM/SGLang/NIM), the thinking-mode caveats, the provider-relayed cache-counter stance, the self-hosted GPU-node bring-up path, and the prominent warning that the deepseek-chat / deepseek-reasoner aliases retire 2026-07-24 15:59 UTC.\"\n---\n\n# DeepSeek V4 through fak\n\nfak fronts DeepSeek as a wire, the same way it fronts any provider: it **governs\nand relays; it does not generate**. This page is the operator's exact-command\nreference for the three DeepSeek V4 routes, the thinking-mode caveats that bite in\npractice, and the honest limits. It is grounded in the official DeepSeek docs\n(researched **July 2026**, linked in [Sources](#sources-researched-july-2026)).\n\n> ## ⚠️ Alias retirement — `deepseek-chat` / `deepseek-reasoner` retire 2026-07-24 15:59 UTC\n>\n> The `deepseek-chat` and `deepseek-reasoner` model ids are **compatibility\n> aliases only** and retire on **2026-07-24 15:59 UTC**\n> ([vendor announcement](https://api-docs.deepseek.com/news/news260424)). Pin\n> **`deepseek-v4-pro`** (or `deepseek-v4-flash`) explicitly. Any script, account\n> profile, or harness config still naming an alias after the cutoff will break —\n> migrate now.\n\n## The three routes at a glance\n\n| Route | When to use it | Model id | Base URL |\n|---|---|---|---|\n| **Hosted OpenAI-compatible** | Any OpenAI-Chat-Completions client through `fak serve`/`fak guard` | `deepseek-v4-pro` | `https://api.deepseek.com` |\n| **Anthropic-compatible** | Claude Code-style clients that speak Anthropic Messages | `deepseek-v4-pro` | `https://api.deepseek.com/anthropic` |\n| **Self-hosted (vLLM/SGLang/NIM)** | Your own GPUs (Hopper/Blackwell-class node) | `deepseek-ai/DeepSeek-V4-Pro` / `-Flash` | `http://:8000/v1` |\n\nAll examples use the `DEEPSEEK_API_KEY` environment variable and **never a literal\nkey**.\n\n### 1. Hosted OpenAI-compatible route\n\nThe hosted DeepSeek API exposes the OpenAI Chat Completions surface. Front it with\nfak as a generic OpenAI-compatible upstream:\n\n```bash\nexport DEEPSEEK_API_KEY=... # your key; never commit it\n\nfak serve --provider openai-compatible \\\n --base-url \"https://api.deepseek.com\" \\\n --model \"deepseek-v4-pro\" \\\n --api-key-env DEEPSEEK_API_KEY\n```\n\nThe first request uses model `deepseek-v4-pro`, an OpenAI-compatible base URL, and\nsupports `thinking`, `reasoning_effort`, and the streaming toggle\n([vendor quick start](https://api-docs.deepseek.com/)).\n\n### 2. Anthropic-compatible route (Claude Code-style clients)\n\nDeepSeek also exposes an **Anthropic Messages**-compatible surface at\n`https://api.deepseek.com/anthropic`\n([coding-agents guide](https://api-docs.deepseek.com/guides/coding_agents)). A\nClaude Code-style client can point at fak, which fronts that surface:\n\n```bash\nexport DEEPSEEK_API_KEY=...\n\nfak serve --provider anthropic \\\n --base-url \"https://api.deepseek.com/anthropic\" \\\n --model \"deepseek-v4-pro\" \\\n --api-key-env DEEPSEEK_API_KEY\n```\n\n### 3. Self-hosted route (your own GPUs)\n\nRun the model on a tuned engine exposing the OpenAI surface, then front it. This\nis the path for a self-hosted bring-up — see [Bring V4 up on a GPU node](#bring-v4-up-on-a-gpu-node)\nfor the one-command script and the **architecture floor** you must confirm first.\n\n```bash\n# On the serving node (see the self-host runbook for engine flags):\nvllm serve deepseek-ai/DeepSeek-V4-Pro --served-model-name deepseek-ai/DeepSeek-V4-Pro --port 8000\n# → OpenAI root: http://:8000/v1\n\n# Then, from anywhere that can reach it:\nfak serve --provider openai-compatible \\\n --base-url \"http://:8000/v1\" \\\n --model \"deepseek-ai/DeepSeek-V4-Pro\"\n```\n\nfak makes **no claim to natively run** the 1.6T-total V4-Pro weights; the model is\nserved by vLLM/SGLang/NIM and fak sits in front. Native in-kernel loader work is a\nseparately scoped effort (parent program\n[#3006](https://github.com/anthony-chaudhary/fak/issues/3006)).\n\n## Thinking-mode caveats (these bite in practice)\n\nDeepSeek V4 is a reasoning model. The behavior differs from a plain chat model in\nways that affect harness integration:\n\n- **`thinking` defaults to ENABLED.** A turn may spend its whole token budget on\n reasoning and return **empty `content` with populated `reasoning_content`** —\n that is a live, correct response, not a failure. (fak's self-host smoke treats a\n reasoning-only turn as a live wire for exactly this reason.)\n- **`reasoning_effort` accepts only `high` / `max`.** There is no low/medium tier.\n- **Sampling controls do not affect thinking mode.** `temperature`, `top_p`,\n `presence_penalty`, and `frequency_penalty` have **no effect** on the thinking\n path — do not expect them to steer reasoning.\n- **Tool-call turns require reasoning-content transcript preservation.** When a\n turn makes a tool call, the `reasoning_content` from that turn must be carried\n forward in the transcript for the follow-up turn to be correct. fak preserves\n DeepSeek reasoning content across tool-call turns (see the gateway/agent\n reasoning-preservation coverage); a harness that strips it will degrade the\n model.\n\n## Cache counters are provider-relayed, not fak-authored savings\n\nDeepSeek reports prompt-cache hit/miss token counts in its usage block. fak\n**relays and prices these as the provider reports them** — they are\n**provider-relayed, provider-priced** counters. They are **not** a fak-authored\nsaving and must never be presented as fak cache savings. fak's own cache economics\n(the addressable in-kernel cache) are a distinct, separately-witnessed subsystem.\n\n## Bring V4 up on a GPU node\n\nThe one-command path is [`scripts/dgx-deepseek-serve.sh`](../../scripts/dgx-deepseek-serve.sh):\nit detects your GPUs, **gates on the DeepSeek-V4 architecture floor**, launches the\nOpenAI server on `:8000`, health-checks `/models`, and prints the exact `fak serve`\n+ wire-witness commands.\n\n> **Architecture floor — confirm before reserving GPU time.** V4 checkpoints are\n> **FP4** (MoE experts) + **FP8** (most params) with an **FP4 attention indexer**,\n> and the attention stack is **DSA (DeepSeek Sparse Attention)**. **Stock** kernels\n> (vLLM/SGLang/NIM) generally need **sm_90+ (Hopper)**, with **sm_100 (Blackwell)**\n> for native NVFP4. `scripts/dgx-deepseek-serve.sh` fronts a stock engine and warns\n> and refuses below the floor unless `FAK_DGX_FORCE=1`. V4-**Pro** (1.6T total) is a\n> **multi-node** expert-parallel deployment; size a single node against V4-**Flash**\n> first.\n>\n> **sm_80 (Ampere/A100) is no longer dead-ended.** A V4-aware **llama.cpp fork**\n> ([cchuter `feat/v4-port-cuda`](https://github.com/cchuter/llama.cpp); upstream\n> [PR #24162](https://github.com/ggml-org/llama.cpp/pull/24162) for Unsloth quants)\n> serves **V4-Flash** on sm_80 via CUDA V4-op kernels + a software-emulated FP8 path\n> — the same route that overcame the sm_90 wall for GLM-5.2 on A100. V4-Flash GGUFs\n> (≈103–162 GB) fit **resident** on 8×A100-80 GB. The one-command harness is\n> [`tools/deepseekv4_stage_serve_gpu_server.sh`](../../tools/deepseekv4_stage_serve_gpu_server.sh).\n> This is the interim serve path; the fak-native V4 kernel is a separate track\n> ([#3016](https://github.com/anthony-chaudhary/fak/issues/3016)–[#3019](https://github.com/anthony-chaudhary/fak/issues/3019)).\n\nThe full procedure, the minimum-evidence witness ladder, and the comparability\nboundary are in the\n[self-host baseline runbook](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md).\n\n### Wire witness (what fak has proven)\n\nfak's `openai-compatible` route has been **witnessed live against a real DeepSeek-V4\nendpoint** (`deepseek-ai/deepseek-v4-flash`), passing all three rungs — readiness,\nnon-streaming (with honest usage counters), and streaming to `[DONE]`. See the\nrunbook's [Live witness](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md#live-witness--wire-proven-on-a-real-v4-endpoint)\nsection. This proves the **wire**; a **performance headline** still requires the\ntuned EP/EPLB baseline on a dedicated node and is not claimed here.\n\n## Budget before you claim 1M context\n\nBefore quoting 1M-context support, model the memory/KV economics with the\nchecked-in `fak`-side budget calculator (issue\n[#3015](https://github.com/anthony-chaudhary/fak/issues/3015),\n`internal/gateway/deepseek_budget.go`): it estimates FP4+FP8 weight storage,\nactivated-parameter compute proxy, and compressed-KV vs SWA/state-cache tail\npressure per model and context length, labelling every row **MODELED** until fed by\nwitnessed serving telemetry.\n\n## Acceptance mapping (issue #3012)\n\n- **Supported-provider page added** — this page.\n- **Copy/paste commands for all three routes** — [above](#the-three-routes-at-a-glance),\n each using `DEEPSEEK_API_KEY`, never a literal key.\n- **Thinking-mode caveats documented** — [Thinking-mode caveats](#thinking-mode-caveats-these-bite-in-practice):\n `thinking` default-on, `reasoning_effort` high/max only, sampling controls inert,\n tool-call reasoning-content preservation.\n- **Provider cache counters framed as provider-relayed/provider-priced** —\n [Cache counters](#cache-counters-are-provider-relayed-not-fak-authored-savings).\n- **Alias-retirement warning prominent** — the top-of-page callout (2026-07-24\n 15:59 UTC).\n- **Links to official sources with the research date** —\n [Sources](#sources-researched-july-2026).\n- **`rg \"deepseek-chat|deepseek-reasoner\" docs examples`** finds only this\n migration/compatibility context.\n\n## Sources (researched July 2026)\n\n- DeepSeek API quick start (OpenAI-compatible base URL, `thinking`,\n `reasoning_effort`, streaming): \n- Coding-agents guide (Anthropic-compatible route for Claude Code/OpenCode):\n \n- Pricing / model page (capabilities, context, max output, alias retirement):\n \n- V4 preview / alias-retirement announcement (`deepseek-chat` / `deepseek-reasoner`\n retire 2026-07-24 15:59 UTC): \n- V4-Pro model card (FP4+FP8 mixed precision, MoE experts FP4):\n \n\n## Cross-links\n\n- [Clouds & hosted providers](clouds.md) — the full hosted-provider list.\n- [Self-host baseline runbook](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md)\n · [Perf scorecard](../benchmarks/DEEPSEEK-V4-PERF-SCORECARD.md)\n- [Model accounts](../model-accounts.md) · Parent program\n [#3006](https://github.com/anthony-chaudhary/fak/issues/3006).\n"}, "ba6cefea97eb4bfcb179c06a05ee190c4c84c833": {"t": 1786068463.5263062, "n": 10736, "h": "5e88f18cb5b667e8", "c": "---\ntitle: \"DeepSeek V4 through fak — hosted, Anthropic-compatible, and self-hosted routes\"\ndescription: \"The exact copy/paste commands for the three DeepSeek V4 routes fak fronts (hosted OpenAI-compatible, Anthropic-compatible for Claude Code-style clients, and self-hosted vLLM/SGLang/NIM), the thinking-mode caveats, the provider-relayed cache-counter stance, the self-hosted GPU-node bring-up path, and the prominent warning that the deepseek-chat / deepseek-reasoner aliases retire 2026-07-24 15:59 UTC.\"\n---\n\n# DeepSeek V4 through fak\n\nfak fronts DeepSeek as a wire, the same way it fronts any provider: it **governs\nand relays; it does not generate**. This page is the operator's exact-command\nreference for the three DeepSeek V4 routes, the thinking-mode caveats that bite in\npractice, and the honest limits. It is grounded in the official DeepSeek docs\n(researched **July 2026**, linked in [Sources](#sources-researched-july-2026)).\n\n> ## ⚠️ Alias retirement — `deepseek-chat` / `deepseek-reasoner` retire 2026-07-24 15:59 UTC\n>\n> The `deepseek-chat` and `deepseek-reasoner` model ids are **compatibility\n> aliases only** and retire on **2026-07-24 15:59 UTC**\n> ([vendor announcement](https://api-docs.deepseek.com/news/news260424)). Pin\n> **`deepseek-v4-pro`** (or `deepseek-v4-flash`) explicitly. Any script, account\n> profile, or harness config still naming an alias after the cutoff will break —\n> migrate now.\n\n## The three routes at a glance\n\n| Route | When to use it | Model id | Base URL |\n|---|---|---|---|\n| **Hosted OpenAI-compatible** | Any OpenAI-Chat-Completions client through `fak serve`/`fak guard` | `deepseek-v4-pro` | `https://api.deepseek.com` |\n| **Anthropic-compatible** | Claude Code-style clients that speak Anthropic Messages | `deepseek-v4-pro` | `https://api.deepseek.com/anthropic` |\n| **Self-hosted (vLLM/SGLang/NIM)** | Your own GPUs (Hopper/Blackwell-class node) | `deepseek-ai/DeepSeek-V4-Pro` / `-Flash` | `http://:8000/v1` |\n\nAll examples use the `DEEPSEEK_API_KEY` environment variable and **never a literal\nkey**.\n\n### 1. Hosted OpenAI-compatible route\n\nThe hosted DeepSeek API exposes the OpenAI Chat Completions surface. Front it with\nfak as a generic OpenAI-compatible upstream:\n\n```bash\nexport DEEPSEEK_API_KEY=... # your key; never commit it\n\nfak serve --provider openai-compatible \\\n --base-url \"https://api.deepseek.com\" \\\n --model \"deepseek-v4-pro\" \\\n --api-key-env DEEPSEEK_API_KEY\n```\n\nThe first request uses model `deepseek-v4-pro`, an OpenAI-compatible base URL, and\nsupports `thinking`, `reasoning_effort`, and the streaming toggle\n([vendor quick start](https://api-docs.deepseek.com/)).\n\n### 2. Anthropic-compatible route (Claude Code-style clients)\n\nDeepSeek also exposes an **Anthropic Messages**-compatible surface at\n`https://api.deepseek.com/anthropic`\n([coding-agents guide](https://api-docs.deepseek.com/guides/coding_agents)). A\nClaude Code-style client can point at fak, which fronts that surface:\n\n```bash\nexport DEEPSEEK_API_KEY=...\n\nfak serve --provider anthropic \\\n --base-url \"https://api.deepseek.com/anthropic\" \\\n --model \"deepseek-v4-pro\" \\\n --api-key-env DEEPSEEK_API_KEY\n```\n\n### 3. Self-hosted route (your own GPUs)\n\nRun the model on a tuned engine exposing the OpenAI surface, then front it. This\nis the path for a self-hosted bring-up — see [Bring V4 up on a GPU node](#bring-v4-up-on-a-gpu-node)\nfor the one-command script and the **architecture floor** you must confirm first.\n\n```bash\n# On the serving node (see the self-host runbook for engine flags):\nvllm serve deepseek-ai/DeepSeek-V4-Pro --served-model-name deepseek-ai/DeepSeek-V4-Pro --port 8000\n# → OpenAI root: http://:8000/v1\n\n# Then, from anywhere that can reach it:\nfak serve --provider openai-compatible \\\n --base-url \"http://:8000/v1\" \\\n --model \"deepseek-ai/DeepSeek-V4-Pro\"\n```\n\nfak makes **no claim to natively run** the 1.6T-total V4-Pro weights; the model is\nserved by vLLM/SGLang/NIM and fak sits in front. Native in-kernel loader work is a\nseparately scoped effort (parent program\n[#3006](https://github.com/anthony-chaudhary/fak/issues/3006)).\n\n## Thinking-mode caveats (these bite in practice)\n\nDeepSeek V4 is a reasoning model. The behavior differs from a plain chat model in\nways that affect harness integration:\n\n- **`thinking` defaults to ENABLED.** A turn may spend its whole token budget on\n reasoning and return **empty `content` with populated `reasoning_content`** —\n that is a live, correct response, not a failure. (fak's self-host smoke treats a\n reasoning-only turn as a live wire for exactly this reason.)\n- **`reasoning_effort` accepts only `high` / `max`.** There is no low/medium tier.\n- **Sampling controls do not affect thinking mode.** `temperature`, `top_p`,\n `presence_penalty`, and `frequency_penalty` have **no effect** on the thinking\n path — do not expect them to steer reasoning.\n- **Tool-call turns require reasoning-content transcript preservation.** When a\n turn makes a tool call, the `reasoning_content` from that turn must be carried\n forward in the transcript for the follow-up turn to be correct. fak preserves\n DeepSeek reasoning content across tool-call turns (see the gateway/agent\n reasoning-preservation coverage); a harness that strips it will degrade the\n model.\n\n## Cache counters are provider-relayed, not fak-authored savings\n\nDeepSeek reports prompt-cache hit/miss token counts in its usage block. fak\n**relays and prices these as the provider reports them** — they are\n**provider-relayed, provider-priced** counters. They are **not** a fak-authored\nsaving and must never be presented as fak cache savings. fak's own cache economics\n(the addressable in-kernel cache) are a distinct, separately-witnessed subsystem.\n\n## Bring V4 up on a GPU node\n\nThe one-command path is [`scripts/dgx-deepseek-serve.sh`](../../scripts/dgx-deepseek-serve.sh):\nit detects your GPUs, **gates on the DeepSeek-V4 architecture floor**, launches the\nOpenAI server on `:8000`, health-checks `/models`, and prints the exact `fak serve`\n+ wire-witness commands.\n\n> **Architecture floor — confirm before reserving GPU time.** V4 checkpoints are\n> **FP4** (MoE experts) + **FP8** (most params) with an **FP4 attention indexer**,\n> and the attention stack is **DSA (DeepSeek Sparse Attention)**. **Stock** kernels\n> (vLLM/SGLang/NIM) generally need **sm_90+ (Hopper)**, with **sm_100 (Blackwell)**\n> for native NVFP4. `scripts/dgx-deepseek-serve.sh` fronts a stock engine and warns\n> and refuses below the floor unless `FAK_DGX_FORCE=1`. V4-**Pro** (1.6T total) is a\n> **multi-node** expert-parallel deployment; size a single node against V4-**Flash**\n> first.\n>\n> **sm_80 (Ampere/A100) is no longer dead-ended.** A V4-aware **llama.cpp fork**\n> ([cchuter `feat/v4-port-cuda`](https://github.com/cchuter/llama.cpp); upstream\n> [PR #24162](https://github.com/ggml-org/llama.cpp/pull/24162) for Unsloth quants)\n> serves **V4-Flash** on sm_80 via CUDA V4-op kernels + a software-emulated FP8 path\n> — the same route that overcame the sm_90 wall for GLM-5.2 on A100. V4-Flash GGUFs\n> (≈103–162 GB) fit **resident** on 8×A100-80 GB. The one-command harness is\n> [`tools/deepseekv4_stage_serve_gpu_server.sh`](../../tools/deepseekv4_stage_serve_gpu_server.sh).\n> This is the interim serve path; the fak-native V4 kernel is a separate track\n> ([#3016](https://github.com/anthony-chaudhary/fak/issues/3016)–[#3019](https://github.com/anthony-chaudhary/fak/issues/3019)).\n\nThe full procedure, the minimum-evidence witness ladder, and the comparability\nboundary are in the\n[self-host baseline runbook](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md).\n\n### Wire witness (what fak has proven)\n\nfak's `openai-compatible` route has been **witnessed live against a real DeepSeek-V4\nendpoint** (`deepseek-ai/deepseek-v4-flash`), passing all three rungs — readiness,\nnon-streaming (with honest usage counters), and streaming to `[DONE]`. See the\nrunbook's [Live witness](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md#live-witness--wire-proven-on-a-real-v4-endpoint)\nsection. This proves the **wire**; a **performance headline** still requires the\ntuned EP/EPLB baseline on a dedicated node and is not claimed here.\n\n## Budget before you claim 1M context\n\nBefore quoting 1M-context support, model the memory/KV economics with the\nchecked-in `fak`-side budget calculator (issue\n[#3015](https://github.com/anthony-chaudhary/fak/issues/3015),\n`internal/gateway/deepseek_budget.go`): it estimates FP4+FP8 weight storage,\nactivated-parameter compute proxy, and compressed-KV vs SWA/state-cache tail\npressure per model and context length, labelling every row **MODELED** until fed by\nwitnessed serving telemetry.\n\n## Acceptance mapping (issue #3012)\n\n- **Supported-provider page added** — this page.\n- **Copy/paste commands for all three routes** — [above](#the-three-routes-at-a-glance),\n each using `DEEPSEEK_API_KEY`, never a literal key.\n- **Thinking-mode caveats documented** — [Thinking-mode caveats](#thinking-mode-caveats-these-bite-in-practice):\n `thinking` default-on, `reasoning_effort` high/max only, sampling controls inert,\n tool-call reasoning-content preservation.\n- **Provider cache counters framed as provider-relayed/provider-priced** —\n [Cache counters](#cache-counters-are-provider-relayed-not-fak-authored-savings).\n- **Alias-retirement warning prominent** — the top-of-page callout (2026-07-24\n 15:59 UTC).\n- **Links to official sources with the research date** —\n [Sources](#sources-researched-july-2026).\n- **`rg \"deepseek-chat|deepseek-reasoner\" docs examples`** finds only this\n migration/compatibility context.\n\n## Sources (researched July 2026)\n\n- DeepSeek API quick start (OpenAI-compatible base URL, `thinking`,\n `reasoning_effort`, streaming): \n- Coding-agents guide (Anthropic-compatible route for Claude Code/OpenCode):\n \n- Pricing / model page (capabilities, context, max output, alias retirement):\n \n- V4 preview / alias-retirement announcement (`deepseek-chat` / `deepseek-reasoner`\n retire 2026-07-24 15:59 UTC): \n- V4-Pro model card (FP4+FP8 mixed precision, MoE experts FP4):\n \n\n## Cross-links\n\n- [Clouds & hosted providers](clouds.md) — the full hosted-provider list.\n- [Self-host baseline runbook](../benchmarks/DEEPSEEK-V4-SELFHOST-BASELINE-RUNBOOK.md)\n · [Perf scorecard](../benchmarks/DEEPSEEK-V4-PERF-SCORECARD.md)\n- [Model accounts](../model-accounts.md) · Parent program\n [#3006](https://github.com/anthony-chaudhary/fak/issues/3006).\n"}}, "docs/model-accounts.md": {"f1c36fd45ecfb60dc33288bccf3477b30bbd53cf": {"t": 1785746555.6773813, "n": 43276, "h": "6167b7aaac2b701b", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, DeepSeek, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, Groq, DeepSeek, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product sibling of the fleet account switcher (`fak fleet-accounts`,\nthe native Go successor to the legacy `tools/fleet_accounts.py` shim): provider-neutral,\ncredential-safe, and composable with the routing spine. It lives in\n`internal/modelroute/account.go` (pure, stdlib-only, the same package as the routing\ndecision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\nOpenAI-compatible providers such as Groq are represented as `kind: \"openai\"` plus their\nprovider `base_url`, so the wire adapter stays generic while the account id names whose\ncredential is being used.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" },\n { \"id\": \"july6netra_groq\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 1000,\n \"tokens_per_minute\": 8000, \"tokens_per_day\": 200000 },\n { \"id\": \"july6netra_groq_compound\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 250 },\n { \"id\": \"deepseek\", \"kind\": \"deepseek\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 },\n { \"id\": \"deepseek-anthropic\", \"kind\": \"anthropic\",\n \"base_url\": \"https://api.deepseek.com/anthropic\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" },\n { \"model\": \"qwen36-groq\", \"account\": \"july6netra_groq\", \"upstream_model\": \"qwen/qwen3.6-27b\" },\n { \"model\": \"groq-compound\", \"account\": \"july6netra_groq_compound\", \"upstream_model\": \"groq/compound\" },\n { \"model\": \"deepseek-pro\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-pro\" },\n { \"model\": \"deepseek-flash\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-flash\" },\n { \"model\": \"deepseek-chat-compat\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-chat\",\n \"compatibility_only\": true, \"deprecated_after_utc\": \"2026-07-24 15:59 UTC\",\n \"deprecated_alias_for\": \"deepseek-v4-flash non-thinking mode\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nCoding-agent subscriptions add a second layer: **config-home accounts**. Claude uses\n`CLAUDE_CONFIG_DIR` homes such as `~/.claude-gem8-seat`; Codex uses `CODEX_HOME` homes such as\n`~/.codex` or `~/.codex-work`. `fak fleet-accounts` discovers both, derives their non-secret\nprovider-account identity, collapses duplicate homes on the same rate-limit bucket, and offers\nonly homes whose credential state is ready. The default Codex picker profile is\n`gpt-5.6-sol` with `model_reasoning_effort=xhigh`.\n\nThe authored lifecycle registry behind `fak accounts` remains Claude-specific today; its seat\nnames and tombstones are therefore applied only to Claude rows. Codex rows take lifecycle/login\ntruth from their own home (`auth.json`/`config.toml`) and never inherit a same-named Claude seat\nsuch as `default`.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## API-host probe bridge\n\nThe same model-account roster can feed the API-host no-spend probes:\n\n```bash\nfak api-host readiness --from-model-accounts examples/model-accounts.example.json\nfak api-host acceptance --from-model-accounts examples/model-accounts.example.json\n```\n\n`readiness` converts only OpenAI-compatible or local accounts into `/models` probe targets\n(`openai`, `openai-responses`, `xai`, `deepseek`, and `local`). Native provider accounts are not guessed into\nan OpenAI-shaped probe. `acceptance` keeps every account with a probeable base URL visible:\nOpenAI-compatible/local accounts can become `READY_FOR_LIVE_BRIDGE_RUN`, while native\nAnthropic/Gemini accounts are reported as `WIRE_SUPPORTED_UNPROBED`. Model hints come from the\nroster bindings, so the API-host report stays tied to the same abstract model ids the route policy\nuses.\n\nGroq Qwen3.6 uses the OpenAI-compatible base `https://api.groq.com/openai/v1`, credential\nenv var `FAK_GROQ_API_KEY`, and upstream model slug `qwen/qwen3.6-27b`. The example records\nthe current account/model limits as metadata: 30 requests/minute, 1,000 requests/day,\n8,000 tokens/minute, and 200,000 tokens/day. Groq Compound uses the same base and env var\nunder the separate account id `july6netra_groq_compound`, bound as the lower-quality\n`groq-compound` target to upstream slug `groq/compound`; its metadata is request-only:\n30 requests/minute and 250 requests/day, with no token-minute or token-day cap recorded.\nThe Claude dogfood launcher separately caps the outbound `max_tokens` field at 8192 for\n`groq/compound`, because Groq rejects larger per-response output budgets. Those values\nare advisory metadata for routing and launchers; the v1 resolver does not throttle\nrequests.\n\nDeepSeek V4 uses `deepseek-v4-pro` / `deepseek-v4-flash` on the OpenAI-compatible base\n`https://api.deepseek.com`; its Anthropic-compatible profile uses\n`https://api.deepseek.com/anthropic`. The legacy `deepseek-chat` and `deepseek-reasoner`\naliases are accepted only when marked `compatibility_only`, with the documented retirement\nmetadata `2026-07-24 15:59 UTC`, so a new roster does not silently bind to an alias that is\nabout to disappear.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open. The same file pins the tier-1 mirror\n(`modelroute.IsRemoteRoute`) against the enforcing floor over one corpus, because\n`internal/modelroute` sits below `internal/engine` and must keep its own copy of the\non-box family list. See *Placement zones* below for the third rung the binary\nlocal/remote split does not name.\n\n## Placement zones: your box, your company's boxes, someone else's\n\nLocality is not binary. A company that self-hosts runs models in **three** places, and\nan account declares which by its `kind`:\n\n| Zone | `kind` | Where the weights sit | Self-hosted? | On-box? |\n| --- | --- | --- | --- | --- |\n| `device` | `local` | the engineer's own machine (loopback only) | yes | **yes** |\n| `fleet` | `fleet` | a machine the **organization** operates | yes | no |\n| `vendor` | `openai` / `anthropic` / `gemini` / `xai` / `deepseek` / `openai-responses` | a third-party lab's API | no | no |\n\nThe middle rung is the one that carries the token volume in a self-hosting shop — a\nGLM- or Kimi-class open model on company GPUs, shared by every engineer. Before\n`kind: fleet` existed it could not be written down: as `local` it was refused (a local\naccount must carry a loopback base URL), and as `openai` it was admitted but became\nindistinguishable from `api.openai.com` in every downstream record, so the org got no\ncredit for hardware it owns.\n\n```json\n{\n \"id\": \"corp-glm\",\n \"kind\": \"fleet\",\n \"base_url\": \"http://glm.infer.corp.internal:8000/v1\",\n \"label\": \"GLM-5.2 vLLM server the company operates\"\n}\n```\n\nA `fleet` account needs an explicit, non-loopback `http(s)` endpoint (there is no public\ndefault for a host only your org can name, and a loopback address is `device` by\ndefinition — the zones must stay disjoint). Unlike a vendor account it does **not**\nrequire a `cred_env`: an org-operated server on a private network commonly has no API\nkey. Supply one when the endpoint authenticates.\n\n**Self-hosted is not the same as on-box, deliberately.** `ZoneFleet.SelfHosted()` is\ntrue — its tokens count toward \"we ran this on our own silicon\" — but\n`ZoneFleet.OnBox()` is false, so the residency floor still treats a `fleet:…` route as\nremote and still denies a tenant-scoped payload routed there. Naming the zone changes\nwhat fak can *attribute*, not what it *permits*. Letting an org-operated host carry a\nsensitive payload is an enforcement change that needs an operator-declared trust\nboundary (authenticated transport, a named host allowlist), and it is tracked\nseparately — it does not arrive as a side effect of declaring a zone.\n\nThe zones are ordered as an escalation ladder — `device` (0) → `fleet` (1) →\n`vendor` (2) — so \"prefer the cheapest rung that can do the work\" is a comparison on a\ndeclared value rather than a string switch. An unrecognized zone ranks *above* vendor\nand is neither self-hosted nor on-box: unattributable never reads as free or as safe.\n\n### Walking the ladder: `Roster.Place`\n\n`Roster.Place(class, candidates)` is what actually walks it. Given a `WorkClass` and the\nmodels you have bound — each with the work tier you have evidence it can serve — it\nreturns the cheapest rung that can take the job, plus the full ladder walk that produced\nthe answer:\n\n```go\np, err := roster.Place(modelroute.ClassRoutine, []modelroute.Candidate{\n {Model: \"tiny\", Capability: modelroute.TierT2, Measured: true},\n {Model: \"corp-mid\", Capability: modelroute.TierT1, Measured: true},\n {Model: \"frontier\", Capability: modelroute.TierT0, Measured: true},\n})\n// p.Zone == ZoneDevice, p.SelfHosted() == true, p.Escalated == false\n```\n\nTwo rules stop it from being wishful thinking:\n\n**The floor belongs to the work, not to the model.** Admission runs through\n`TierPolicy.Admit`, so a cheap rung cannot take work above its tier however available it\nis. Security/release/destructive work has a floor that never drops to routine, so it\nskips a measured local model and lands on the fleet — `p.Escalated` is true and the\ndevice rung reports `zone-under-tier`. An unrecognized class stays at the strictest floor\nrather than inferring a cheap one.\n\n**Unmeasured capability may not descend the ladder.** `Candidate.Measured` records\nwhether `Capability` is a measurement or an assertion, and an assertion is skipped on\nevery rung below the vendor. This matters right now: `internal/ablate`'s `StubTierScorer`\nmeasures nothing, so a placer that trusted asserted grades would route everything to the\ncheapest rung on a number nobody computed, and would look like it was working. Until\ncapability measurement lands, an operator opts a rung in by marking a candidate\n`Measured` — a claim they can be held to. `TestUnmeasuredCapabilityCannotDescendTheLadder`\npins both halves: an all-asserted ladder places on the vendor, and the same work moves to\nthe device the moment the cheap rung is measured.\n\nEvery rung is reported, including the ones passed over, in a closed reason vocabulary\n(`placed-in-zone`, `zone-under-tier`, `zone-capability-unmeasured`, `zone-no-candidate`,\n`zone-not-reached`, `escalated-past-cheaper-zone`) — because \"why is this *still* going to\na vendor?\" is the question an operator actually asks. A candidate the roster cannot\nresolve is a loud error, never a silent skip: a typo in a placement config must surface as\na misconfiguration rather than as traffic quietly continuing to bill a vendor.\n\nThe trap this code is written around is that `WorkTier` numbers are **inverted** — T0 is\nthe most demanding but the lowest number. Every comparison goes through\n`WorkTier.MeetsRequirement`, never a raw `<` or `>=`. Substituting the natural-looking\n`capability >= required` fails four tests at once, and the way it fails is the point: a\n4B laptop model takes both the ultra-hard and the security/destructive work.\n\n### Declaring what the work is\n\n`Place` needs a `WorkClass`; routing produces a `Subject`. `ClassOf(Subject)` is the\njoint, and it is where an automatic placer is most tempted to cheat — it would be easy to\nlook at a tool name, decide \"grep is harmless\", and route it to a laptop. fak does not do\nthat. Judging what a call can destroy is `internal/adjudicator`'s reversibility question,\nit sits a tier above this leaf, and it is deliberately not re-guessed from a name here.\n\nSo a work class is **declared, or it is conservative**. Declare it as a subject label:\n\n```jsonc\n{ \"match\": { \"aspect\": \"tool_call\", \"labels\": { \"work_class\": \"routine\" } },\n \"plan\": { \"model\": \"tiny\" } }\n```\n\n| Input | Classified as | Why |\n| --- | --- | --- |\n| `labels.work_class` naming a known class | that class, **declared** | the operator said so |\n| `aspect: \"scout\"`, nothing declared | `routine`, **declared** | a scout *is* the cheap classify-first probe — the aspect names the work |\n| anything else | *(empty)*, **undeclared** | `PolicyFor` already puts the empty class at the strictest floor |\n| `labels.work_class` misspelled | *(empty)*, **undeclared** | a typo in `routine` must not read as permission to use a laptop |\n\nUndeclared work therefore lands exactly where it landed before this feature existed — on\na vendor — and reports `class-undeclared-conservative` so an operator can see the fix is\na label rather than more hardware. There is no second conservatism mechanism layered on\ntop; one gate in one place is why the behaviour stays predictable. `Complexity` can then\nratchet a floor **up** (a high-complexity `routine` subject becomes `normal-impl`) and\nnever down, which is what makes accepting complexity as an input safe at all.\n\n`Roster.PlaceSubject(subject, candidates)` composes the two and returns the placement\n*and* the classification, so both halves of the decision are visible together.\n\n### Declaring what a sub-agent type does\n\nSub-agents are where the cheap rungs would pay for themselves — a fleet spawns far more\ndelegated work than top-level turns — and `Roster.PlaceSpawn(parent, class, candidates)`\nalready answers \"which rung should this child run on\". It refuses an **empty** work class\non purpose: an unclassified spawn is a missing classification, not a routine one, and\nletting delegation reach a laptop without stating what the work *is* would be exactly the\nshape a floor bypass takes.\n\nThat refusal is what left the call unreachable. A spawn arrives as an admitted tool call\ncarrying an agent **type** and a prompt. The prompt is prose, and reading a class out of\nprose is the guess `ClassOf` refuses everywhere else. The type is structured — but it is\nnot a work class: only an operator knows whether the agent type *their* fleet calls\n`explore` does bounded lookup or ships code.\n\nSo it is declared, in the roster the operator already owns:\n\n```jsonc\n\"spawn_classes\": [\n { \"type\": \"explore\", \"class\": \"routine\" },\n { \"type\": \"code-reviewer\", \"class\": \"normal-impl\" },\n { \"type\": \"release-cutter\", \"class\": \"security-release-destructive\" }\n]\n```\n\n`Roster.SpawnClassFor(type)` resolves one, returning `(class, declared bool)`. The rules\nare the ones the rest of this leaf runs on:\n\n| Input | Resolves to | Why |\n| --- | --- | --- |\n| a declared type (case- and space-insensitive) | that class, **declared** | the operator said so |\n| a type nobody declared, or no `spawn_classes` block at all | *(empty)*, **undeclared** | `PlaceSpawn` then refuses — the spawn keeps whatever placement it has today |\n| a declared type whose class token this package does not know | *(empty)*, **undeclared** | same rule as a misspelled `work_class` label: a typo is not permission to use a laptop |\n\nThe match is **exact**, never a prefix and never a glob — `explore` must not answer for\n`explore-and-delete`. A malformed entry is refused at load rather than skipped: an empty\ntype, an empty or unknown class (the error names the four options), a duplicate type, or a\ntype carrying a route delimiter. A silently-dropped entry is the worst outcome available,\nbecause it looks correct from the operator's side while behaving like an undeclared one,\nand nothing points at the typo.\n\n**Shipped:** the declaration, its validation, and the resolver — plus the block in\n`examples/model-accounts.example.json`, which the test suite parses and resolves.\n**Not shipped:** nothing on the spawn path calls `SpawnClassFor` yet. The gateway does\nobserve spawns (`internal/gateway/adjudicate_proposed.go` stamps every admitted tool call\nand already recognises the spawn-shaped ones — that is where `spawn_count` comes from),\nand that is the seam a placement call would bind to, but binding it changes where\ndelegated traffic runs and is a separate, individually reviewable change under\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416). Declaring\n`spawn_classes` today moves no traffic.\n\nOne neighbouring field is *not* this signal, and it has already been mistaken for it: a\nsession's `ParentTrace`/`Generation` is **continuation** lineage — the same agent after a\nbudget-reset re-continuation — written only by `session.Table.Recontinue`. It never marks\na row as somebody's child. `SpawnCount` is the sub-agent axis, and it is parent-side: it\ncounts children a trace spawned, never whose child a trace is.\n\n### Seeing the ladder before it moves traffic: `fak route --place`\n\n```console\n$ fak route --accounts accounts.json --place --labels work_class=routine\n```\n\nThe oracle walks the ladder for one subject against your real roster and prints the rung\nit chose, every rung it passed over, and the closed reason token for each. It exists\n*before* anything on the dispatch path calls `Place`, deliberately: a placement policy\nnobody can inspect is a policy nobody can be held to, and an operator has to be able to\nsee what the ladder would do with **their** roster before fak starts moving traffic on\nthe strength of it. Add `--json` for the same answer as a report.\n\nThe candidate pool is every model the roster **binds**, not just the one routing picked —\nthe question is which rung can serve this class of work at all. Compatibility-only and\ndeprecated-alias bindings are excluded, since admitting a legacy spelling would let the\nsame hardware appear twice on a rung.\n\nAdd `--spawn-type TYPE` to ask the *delegated* question in the same breath: where would a\nsub-agent of that type run, given that this turn ran where the block above says. Against\nthe shipped example roster:\n\n```console\n$ fak route --accounts examples/model-accounts.example.json --place \\\n --labels work_class=ultra-hard --spawn-type explore \\\n --capability zone-device=t2,zone-fleet=t1,large=t0\n```\n```\nSPAWN PLACEMENT (--spawn-type explore)\n work class routine [declared by the roster's spawn_classes]\n parent zone=vendor model=deepseek-flash\n placed zone=device model=zone-device\n self-hosted yes escalated no failed-over no\n relation spawn-descended-from-parent-zone spawn-inherit-unmeasured\n descent yes self-hosted descent yes\n```\n\nThe child's class comes from the `spawn_classes` declaration above — never from the\nparent, the prompt, or the spelling of the type — so an **undeclared** type is refused\nwith the roster's own list of declared ones rather than assumed routine. The parent's rung\nis *recorded, not obeyed*: a sub-agent spawned from a frontier turn still lands on the\nlaptop, which is the whole point, and `self-hosted descent` is the event\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416) counts. The report also\nanswers the counterfactual — what *inheriting* the parent's model would have done — and\nsays `UNKNOWN` when the parent was never graded, because \"the status quo was fine here\"\nis a claim that needs a measurement behind it. `--json` carries the same answer under a\n`spawn` key that is **absent** when the question was not asked.\n\n### Saying what a model can do\n\nBecause unmeasured capability may not descend the ladder, a fresh roster places\n*everything* on the vendor rung — correctly, and uselessly. There are three ways to give\nthe ladder something to descend on, in increasing order of what they cost to produce:\n\n| Flag | What it is | What it costs to be wrong |\n| --- | --- | --- |\n| `--capability qwen3.6-4b=t2,glm-5.2=t1` | an operator **asserting** a grade | attributable to a person, but does not scale past a handful of models |\n| `--evidence FILE` | a summary of observed outcomes, graded here | someone assembled the counts; you are trusting their arithmetic |\n| `--outcomes FILE` | the append-only turn journal, **counted here** | nothing is trusted but the record itself |\n\nAll three feed one grader (`modelroute.GradeCapability`), and its bar is yours:\n\n```console\n$ fak route --accounts accounts.json --place --labels work_class=routine \\\n --outcomes turns.jsonl --since 30d --grade-floor attempts=50,rate=0.9,witness\n```\n\nThe default bar is 20 independently verified attempts at 80%. There is deliberately **no\nknob that makes self-report count** — a model's own claim of 500/500 successes buys\nnothing, and the refused attempts are reported *as refused* rather than as absent. An\noperator who wants to assert a capability can already do so, attributably, with\n`--capability`. A model claimed by both an assertion and a measurement is a named\nrefusal: they cannot both be the grade.\n\nA grade is the **floor** of the work observed, never its optimal tier, and a model that\nfails the bar is `UNMEASURED` **with a reason** rather than graded at the worst tier. The\nthree reasons call for opposite responses and are never collapsed into one:\n\n| Reason | The fix |\n| --- | --- |\n| `no-trusted-evidence` | run the turns under a witness or a judge — the volume is there, the provenance is not |\n| `insufficient-samples` | run more turns, or lower `attempts=` if you meant to |\n| `below-success-floor` | this model cannot do this work; that is the answer |\n\n### Grading from the turn journal\n\n`--outcomes` reads the record itself — one JSON object per line, appended as turns\nhappen (`modelroute.TurnOutcome`):\n\n```jsonl\n{\"id\":\"t-4471\",\"model\":\"qwen3.6-4b\",\"class\":\"routine\",\"zone\":\"device\",\"success\":true,\"verify\":\"witness\",\"at\":\"2026-07-24T11:02:00Z\"}\n```\n\nThe fold is written to **lose** evidence rather than invent it, because each alternative\ninflates a claim, and it reports every loss:\n\n```\n journal turns.jsonl: 1463 line(s), 1332 counted, 4 model(s) with evidence\n not counted 1 unparseable line(s), 30 replayed id(s), 60 older than 30d, 40 undated\n```\n\n- A repeated `id` is counted **once**. Replaying a file is the cheapest possible way to\n manufacture a grade: the same 20 successes appended three times must not read as 60.\n- An **undated** row cannot be shown to be inside a window, so `--since` excludes it — and\n counts it apart from a stale one, because one needs a wider window and the other needs a\n producer that stamps its rows. Ask for no window and a missing date costs nothing.\n- **Provenance is never merged.** 100 self-reported turns and 20 witnessed ones about the\n same model stay two rows; merging them would force a pick of provenance, and any pick\n is a lie. The grade keeps the weakest provenance behind whatever it merged.\n- A **torn line** is skipped and counted, never fatal. A fleet appending to this file will\n eventually die mid-write, and discarding thousands of verified turns over one partial\n row would make the honest path the fragile one.\n- Rows with no `id` are kept — a missing id is not proof of a duplicate — but reported as\n a corpus that **cannot be checked for replay**, since that is a fact about how much the\n number is worth.\n\n`--evidence` and `--outcomes` are mutually exclusive: a run that took some of its answer\nfrom a journal and some from a hand-written summary produces a grade whose provenance\nnobody can state afterwards.\n\n**What is not shipped:** nothing yet *writes* this journal. `internal/ablate`'s\n`StubTierScorer` still grades nothing, so today the file is something you produce — from\nDOS's git witness, a judge score, or your own harness. That producer is the one piece\nstanding between this ladder and traffic that moves by itself.\n\n### Placing against what is actually answering\n\nA grade says what a model *can* do. It says nothing about whether the box is powered on.\n`--serving FILE` hands the ladder a liveness snapshot — what your own probes just saw —\nand the ladder walks past candidates it reports are not serving:\n\n```json\n{\n \"schema\": \"fak.modelroute.serving.v1\",\n \"as_of_unix\": 1785312000,\n \"max_age_seconds\": 120,\n \"covers\": [\"device\", \"fleet\"],\n \"models\": {\n \"zone-device\": {\"state\": \"up\", \"observed_unix\": 1785311950},\n \"zone-fleet\": {\"state\": \"down\", \"observed_unix\": 1785311950},\n \"zone-fleet-agentic\": {\"state\": \"up\", \"observed_unix\": 1785311950}\n }\n}\n```\n\n```console\n$ fak route --accounts examples/model-accounts.example.json --place \\\n --labels work_class=normal-impl --serving serving.json \\\n --capability zone-device=t2,zone-fleet=t1,zone-fleet-agentic=t1,large=t0\n```\n```\n placed zone=fleet model=zone-fleet-agentic\n self-hosted yes escalated yes failed-over yes\n ladder\n device - zone-capability-unmeasured x2 zone-under-tier\n fleet zone-fleet-agentic zone-serving-down placed-in-zone escalated-past-cheaper-zone\n vendor - zone-not-reached\n\nSERVING SNAPSHOT (--serving serving.json)\n as of 1785312000 max age 120s\n covers device fleet\n observations 3, of which 3 name a model this roster binds\n SILENT small tiny-classifier\n the snapshot claims to speak for these candidates' rungs and then says\n nothing about them, so they are passed over as unknown rather than\n assumed well. That is a gap in the probe, not an outage.\n```\n\n**One dead GPU host is not a dead rung.** A fleet is several machines, and abandoning the\nrung because one box rebooted sends every token to a third-party lab to route around a\nneighbour that was idle — so the failover above stayed on the company's own hardware.\n\n| Verdict | What it means | Placed? |\n| --- | --- | --- |\n| `zone-serving-down` | observed unable to serve | passed over |\n| `zone-serving-unknown` | no verdict, on a rung the snapshot *claims* to cover | passed over |\n| `zone-serving-stale` | a verdict that cannot be shown fresh | passed over |\n| `zone-serving-degraded` | serving under strain | **recorded, and placed anyway** |\n\nDegraded is deliberately not actionable: a loaded host still takes work, and shedding at\nthe first sign of queueing inverts self-hosting exactly in the busy hour it is meant for.\n\nThree rules decide the rest, and each is fail-closed in a different direction:\n\n- **Silence only means something where you said it would.** `covers` is the operator\n declaring which rungs the probe actually watches; a candidate the snapshot is silent\n about *inside* that coverage is unknown, not healthy, because a crashed prober must not\n read as a fleet in perfect health. Outside coverage, silence gates nothing.\n- **An observation is always honored**, coverage or not. Coverage governs what silence\n means, never what a verdict means — a report that says a host is down is not overruled\n by a `covers` list that forgot to mention its rung.\n- **Freshness is fail-closed, and so is every way of not having it**: no observation\n stamp, no `as_of_unix`, past `max_age_seconds`, or stamped *after* the report all read\n as stale. A snapshot with a bound and no clock to measure against therefore gates\n everything it covers, which is correct and reads exactly like a total outage — so the\n summary says `STALE-ALL` and names the missing field rather than leaving you to guess.\n\n**`failed-over` is carried apart from `escalated`** because they want opposite fixes.\nEscalated says a cheaper rung could not do this work — a capability fact, stable until\nsomeone changes the roster. Failed-over says a rung that *could* have was routed around\nbecause something was not answering — an operations fact that may already be untrue. A\nbill read without that second bit shows vendor spend attributed to work that \"needed\" a\nvendor, and sends an operator hunting a capability problem that does not exist while the\ndead host stays dead. Both are printed for every placement, including the ones that stayed\ncheap: a failover *within* a rung cost nothing and still means something is down.\n\nThe snapshot and the roster are validated apart and meet only here, so the report also\nnames what neither half can see alone:\n\n```\n observations 2, of which 0 name a model this roster binds\n UNBOUND glm-5.2 kimi-k3\n these observations gate NOTHING: the ladder only ever asks about\n models the roster binds, so a probe filed under an id nothing binds\n is honored nowhere and this run is identical to one with no snapshot.\n Check the ids against the roster's bindings, not its upstream names.\n Each of these is the UPSTREAM name of a candidate: the probe filed its\n result under the name it dialled, and the ladder asks by routed id.\n glm-5.2 -> zone-fleet\n kimi-k3 -> zone-fleet-agentic\n```\n\nThat is the fail-open the flag would otherwise ship: a clean-validating report, an\noperator who believes they gated a dead host, and a placement identical to one with no\nsnapshot at all. The same typo *inside* declared coverage fails the other way instead —\nthe real candidate is now silent on a rung the report speaks for, so it is passed over as\nunknown and a healthy host looks dead. One id appearing in `UNBOUND` and its routed name\nin `SILENT` is the signature of exactly this mistake. Where an upstream name resolves to\n**two** candidates, both are named: two accounts serving the same weights is how a rung\nscales, and one observation cannot speak for both pools — which is why the snapshot is\nkeyed by routed id in the first place.\n\nTwo smaller refusals hold the same line. An unknown field is rejected rather than ignored,\nbecause `max_age_sec` otherwise parses, validates, looks right, and silently switches\nfail-closed freshness *off*. And a named file that cannot be read is an error, never an\nempty report: dropping the flag means \"no liveness signal\", while a missing file means\nyour probe did not run, and placing work as though every host were healthy is precisely\nthe failure the gate exists to prevent. In `--json` the `serving` key is **absent** unless\na snapshot was supplied, so it can never be misread as a probe that saw nothing.\n\n**What is not shipped:** nothing produces this file either. It is deliberately not a probe\nof fak's own — placement is asserted to be pure (same roster, same candidates, same\nreport, same answer), and a CLI that dialled endpoints would be reading a clock and a\nnetwork inside a decision that reads neither. The snapshot is your monitoring's output,\nin fak's vocabulary.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## Managed-cache posture across launchers\n\nEvery fak launcher that fronts an agent with `fak guard` — `fak accounts launch`,\n`fak codex`, and the dispatch worker (`fak dispatch tick`/`auto`) — can hand guard a\n**managed-cache posture** that decides whether the gateway upgrades the stable-prefix\n`cache_control` breakpoint to the **1h-TTL** tier on the outbound Anthropic wire.\n\n**A subscription seat stays passive by default, on purpose.** Guard's own `--managed-cache\nauto` follows a never-speculate rule: it will not manage a wire whose billing it cannot see.\nA Pro/Max seat authenticates with subscription OAuth (`Authorization: Bearer`), whose cache\neconomics guard does not own, so `auto` resolves **passive** there and no launcher silently\nflips it. Nothing changes for a subscription fleet unless you ask for it.\n\n**An API-key-billed fleet reaches ACTIVE without hand-editing any launcher**, via two env\nknobs read once per launch and threaded onto the child's guard argv (not its environment, so\na *resumed* child — whose gateway env is deliberately stripped — keeps the posture):\n\n| Knob | Effect |\n|------|--------|\n| `FAK_GUARD_API_KEY_ENV=ANTHROPIC_API_KEY` | Names the env var holding the API key. On the Anthropic wire this is the explicit opt-IN to API billing that lets `auto` resolve **ACTIVE** (guard bills the key, not the seat's subscription). |\n| `FAK_MANAGED_CACHE=on\\|off\\|auto` | Forces the posture. `on` upgrades regardless of billing; `off` disables; `auto` (default) defers to the billing signal above. |\n\n`fak accounts launch --managed-cache ` and `fak codex --managed-cache ` expose\nthe same lever per-launch (defaulting to `$FAK_MANAGED_CACHE`); the dispatch worker reads the\nenv knobs only. An unknown mode fails loud at the front-ends; the headless worker warns and\nfalls back to `auto` so a fleet turn never dies over a cache-posture typo. Either way the\nguard child prints the RESOLVED posture in its startup banner, so the worker log witnesses\nwhether the 1h upgrade actually engaged. An unconfigured fleet emits no `--managed-cache`\nflag at all — the guard argv is byte-identical to before this knob existed.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch\n([#2528](https://github.com/anthony-chaudhary/fak/issues/2528)). v1 *resolves and prints*\na binding; it does not yet send a request over the wire to your chosen account. Building\nthe planners from the Targets and running an ensemble's members is the additive dispatch\nwiring, the same shipped-vs-deferred split the routing spine uses for the ensemble fold\nversus its execution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n\n**Placement zones — shipped and deferred, precisely.** *Shipped:* the vocabulary. A\ncompany-operated server is expressible (`kind: fleet`), its account validates on its own\nterms, its route string round-trips to `ZoneFleet`, and `PlacementZone.SelfHosted()`\ngives every downstream consumer one predicate to attribute a token by. Witnessed by\n`TestZonesPartitionTheKnownKinds`, `TestEngineRouteCarriesTheZoneBackOut`,\n`TestShippedExampleRosterCarriesAllThreeZones`, and — at the enforcing floor —\n`TestFleetZoneStaysRemoteAtTheFloor` plus `TestTierOneRouteMirrorAgreesWithTheFloor`.\n*Also shipped:* `Roster.Place` **chooses** a zone (see *Walking the ladder* above).\n*Deferred, and deliberately so:* nothing yet calls it on the live dispatch path, the\nserving path does not yet **record** the zone it used, and the residency floor does not\nyet treat a fleet host as inside a trust boundary. Zone-aware placement, the self-hosted\ntoken-fraction metric, and the escalation ladder are separate, individually reviewable\nchanges on top of this vocabulary — not implied by it. They are tracked as\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416).\n\n### The first consumer: honest stratum attribution\n\n`internal/sessionaudit` classifies audited turns into billing buckets. It used to answer\n\"was this self-hosted?\" from the **model id**, which cannot support the claim: Qwen,\nLlama, Mistral, DeepSeek, GLM and Kimi all publish open weights, so each is servable\neither from hardware you operate or from a vendor's API. That produced two contradictions\nin one file — `deepseek*` was bucketed self-hosted while priced from the published\nDeepSeek *API* rate card, and the roster above binds `qwen/qwen3.6-27b` to **Groq**, a\nvendor, while the classifier called those tokens self-hosted.\n\nA model id now buys only a model-family answer (`open-weights (placement unknown)`), and\nplacement comes from a real signal:\n\n| Call | Answers |\n| --- | --- |\n| `ProviderBucket(model)` | whose *model* it is — never where it ran |\n| `BucketForPlacement(model, zone)` | the bucket, given a `PlacementZone` string |\n| `BucketIsSelfHosted(bucket)` | `(selfHosted, known bool)` — two values on purpose |\n\n`BucketIsSelfHosted` returns a second `known` bool so unattributable volume cannot be\nfolded into either side of a self-hosted fraction. `TestPricedModelsAreNeverBucketedSelfHosted`\nmakes the underlying rule executable and total over the rate card: a model billed per token\nby a third party ran on their hardware, so it can never also be bucketed as self-hosted.\n"}, "e9b2c5538788d27a26fc3d16a859f4724076938b": {"t": 1785749567.9823432, "n": 43276, "h": "6167b7aaac2b701b", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, DeepSeek, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, Groq, DeepSeek, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product sibling of the fleet account switcher (`fak fleet-accounts`,\nthe native Go successor to the legacy `tools/fleet_accounts.py` shim): provider-neutral,\ncredential-safe, and composable with the routing spine. It lives in\n`internal/modelroute/account.go` (pure, stdlib-only, the same package as the routing\ndecision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\nOpenAI-compatible providers such as Groq are represented as `kind: \"openai\"` plus their\nprovider `base_url`, so the wire adapter stays generic while the account id names whose\ncredential is being used.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" },\n { \"id\": \"july6netra_groq\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 1000,\n \"tokens_per_minute\": 8000, \"tokens_per_day\": 200000 },\n { \"id\": \"july6netra_groq_compound\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 250 },\n { \"id\": \"deepseek\", \"kind\": \"deepseek\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 },\n { \"id\": \"deepseek-anthropic\", \"kind\": \"anthropic\",\n \"base_url\": \"https://api.deepseek.com/anthropic\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" },\n { \"model\": \"qwen36-groq\", \"account\": \"july6netra_groq\", \"upstream_model\": \"qwen/qwen3.6-27b\" },\n { \"model\": \"groq-compound\", \"account\": \"july6netra_groq_compound\", \"upstream_model\": \"groq/compound\" },\n { \"model\": \"deepseek-pro\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-pro\" },\n { \"model\": \"deepseek-flash\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-flash\" },\n { \"model\": \"deepseek-chat-compat\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-chat\",\n \"compatibility_only\": true, \"deprecated_after_utc\": \"2026-07-24 15:59 UTC\",\n \"deprecated_alias_for\": \"deepseek-v4-flash non-thinking mode\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nCoding-agent subscriptions add a second layer: **config-home accounts**. Claude uses\n`CLAUDE_CONFIG_DIR` homes such as `~/.claude-gem8-seat`; Codex uses `CODEX_HOME` homes such as\n`~/.codex` or `~/.codex-work`. `fak fleet-accounts` discovers both, derives their non-secret\nprovider-account identity, collapses duplicate homes on the same rate-limit bucket, and offers\nonly homes whose credential state is ready. The default Codex picker profile is\n`gpt-5.6-sol` with `model_reasoning_effort=xhigh`.\n\nThe authored lifecycle registry behind `fak accounts` remains Claude-specific today; its seat\nnames and tombstones are therefore applied only to Claude rows. Codex rows take lifecycle/login\ntruth from their own home (`auth.json`/`config.toml`) and never inherit a same-named Claude seat\nsuch as `default`.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## API-host probe bridge\n\nThe same model-account roster can feed the API-host no-spend probes:\n\n```bash\nfak api-host readiness --from-model-accounts examples/model-accounts.example.json\nfak api-host acceptance --from-model-accounts examples/model-accounts.example.json\n```\n\n`readiness` converts only OpenAI-compatible or local accounts into `/models` probe targets\n(`openai`, `openai-responses`, `xai`, `deepseek`, and `local`). Native provider accounts are not guessed into\nan OpenAI-shaped probe. `acceptance` keeps every account with a probeable base URL visible:\nOpenAI-compatible/local accounts can become `READY_FOR_LIVE_BRIDGE_RUN`, while native\nAnthropic/Gemini accounts are reported as `WIRE_SUPPORTED_UNPROBED`. Model hints come from the\nroster bindings, so the API-host report stays tied to the same abstract model ids the route policy\nuses.\n\nGroq Qwen3.6 uses the OpenAI-compatible base `https://api.groq.com/openai/v1`, credential\nenv var `FAK_GROQ_API_KEY`, and upstream model slug `qwen/qwen3.6-27b`. The example records\nthe current account/model limits as metadata: 30 requests/minute, 1,000 requests/day,\n8,000 tokens/minute, and 200,000 tokens/day. Groq Compound uses the same base and env var\nunder the separate account id `july6netra_groq_compound`, bound as the lower-quality\n`groq-compound` target to upstream slug `groq/compound`; its metadata is request-only:\n30 requests/minute and 250 requests/day, with no token-minute or token-day cap recorded.\nThe Claude dogfood launcher separately caps the outbound `max_tokens` field at 8192 for\n`groq/compound`, because Groq rejects larger per-response output budgets. Those values\nare advisory metadata for routing and launchers; the v1 resolver does not throttle\nrequests.\n\nDeepSeek V4 uses `deepseek-v4-pro` / `deepseek-v4-flash` on the OpenAI-compatible base\n`https://api.deepseek.com`; its Anthropic-compatible profile uses\n`https://api.deepseek.com/anthropic`. The legacy `deepseek-chat` and `deepseek-reasoner`\naliases are accepted only when marked `compatibility_only`, with the documented retirement\nmetadata `2026-07-24 15:59 UTC`, so a new roster does not silently bind to an alias that is\nabout to disappear.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open. The same file pins the tier-1 mirror\n(`modelroute.IsRemoteRoute`) against the enforcing floor over one corpus, because\n`internal/modelroute` sits below `internal/engine` and must keep its own copy of the\non-box family list. See *Placement zones* below for the third rung the binary\nlocal/remote split does not name.\n\n## Placement zones: your box, your company's boxes, someone else's\n\nLocality is not binary. A company that self-hosts runs models in **three** places, and\nan account declares which by its `kind`:\n\n| Zone | `kind` | Where the weights sit | Self-hosted? | On-box? |\n| --- | --- | --- | --- | --- |\n| `device` | `local` | the engineer's own machine (loopback only) | yes | **yes** |\n| `fleet` | `fleet` | a machine the **organization** operates | yes | no |\n| `vendor` | `openai` / `anthropic` / `gemini` / `xai` / `deepseek` / `openai-responses` | a third-party lab's API | no | no |\n\nThe middle rung is the one that carries the token volume in a self-hosting shop — a\nGLM- or Kimi-class open model on company GPUs, shared by every engineer. Before\n`kind: fleet` existed it could not be written down: as `local` it was refused (a local\naccount must carry a loopback base URL), and as `openai` it was admitted but became\nindistinguishable from `api.openai.com` in every downstream record, so the org got no\ncredit for hardware it owns.\n\n```json\n{\n \"id\": \"corp-glm\",\n \"kind\": \"fleet\",\n \"base_url\": \"http://glm.infer.corp.internal:8000/v1\",\n \"label\": \"GLM-5.2 vLLM server the company operates\"\n}\n```\n\nA `fleet` account needs an explicit, non-loopback `http(s)` endpoint (there is no public\ndefault for a host only your org can name, and a loopback address is `device` by\ndefinition — the zones must stay disjoint). Unlike a vendor account it does **not**\nrequire a `cred_env`: an org-operated server on a private network commonly has no API\nkey. Supply one when the endpoint authenticates.\n\n**Self-hosted is not the same as on-box, deliberately.** `ZoneFleet.SelfHosted()` is\ntrue — its tokens count toward \"we ran this on our own silicon\" — but\n`ZoneFleet.OnBox()` is false, so the residency floor still treats a `fleet:…` route as\nremote and still denies a tenant-scoped payload routed there. Naming the zone changes\nwhat fak can *attribute*, not what it *permits*. Letting an org-operated host carry a\nsensitive payload is an enforcement change that needs an operator-declared trust\nboundary (authenticated transport, a named host allowlist), and it is tracked\nseparately — it does not arrive as a side effect of declaring a zone.\n\nThe zones are ordered as an escalation ladder — `device` (0) → `fleet` (1) →\n`vendor` (2) — so \"prefer the cheapest rung that can do the work\" is a comparison on a\ndeclared value rather than a string switch. An unrecognized zone ranks *above* vendor\nand is neither self-hosted nor on-box: unattributable never reads as free or as safe.\n\n### Walking the ladder: `Roster.Place`\n\n`Roster.Place(class, candidates)` is what actually walks it. Given a `WorkClass` and the\nmodels you have bound — each with the work tier you have evidence it can serve — it\nreturns the cheapest rung that can take the job, plus the full ladder walk that produced\nthe answer:\n\n```go\np, err := roster.Place(modelroute.ClassRoutine, []modelroute.Candidate{\n {Model: \"tiny\", Capability: modelroute.TierT2, Measured: true},\n {Model: \"corp-mid\", Capability: modelroute.TierT1, Measured: true},\n {Model: \"frontier\", Capability: modelroute.TierT0, Measured: true},\n})\n// p.Zone == ZoneDevice, p.SelfHosted() == true, p.Escalated == false\n```\n\nTwo rules stop it from being wishful thinking:\n\n**The floor belongs to the work, not to the model.** Admission runs through\n`TierPolicy.Admit`, so a cheap rung cannot take work above its tier however available it\nis. Security/release/destructive work has a floor that never drops to routine, so it\nskips a measured local model and lands on the fleet — `p.Escalated` is true and the\ndevice rung reports `zone-under-tier`. An unrecognized class stays at the strictest floor\nrather than inferring a cheap one.\n\n**Unmeasured capability may not descend the ladder.** `Candidate.Measured` records\nwhether `Capability` is a measurement or an assertion, and an assertion is skipped on\nevery rung below the vendor. This matters right now: `internal/ablate`'s `StubTierScorer`\nmeasures nothing, so a placer that trusted asserted grades would route everything to the\ncheapest rung on a number nobody computed, and would look like it was working. Until\ncapability measurement lands, an operator opts a rung in by marking a candidate\n`Measured` — a claim they can be held to. `TestUnmeasuredCapabilityCannotDescendTheLadder`\npins both halves: an all-asserted ladder places on the vendor, and the same work moves to\nthe device the moment the cheap rung is measured.\n\nEvery rung is reported, including the ones passed over, in a closed reason vocabulary\n(`placed-in-zone`, `zone-under-tier`, `zone-capability-unmeasured`, `zone-no-candidate`,\n`zone-not-reached`, `escalated-past-cheaper-zone`) — because \"why is this *still* going to\na vendor?\" is the question an operator actually asks. A candidate the roster cannot\nresolve is a loud error, never a silent skip: a typo in a placement config must surface as\na misconfiguration rather than as traffic quietly continuing to bill a vendor.\n\nThe trap this code is written around is that `WorkTier` numbers are **inverted** — T0 is\nthe most demanding but the lowest number. Every comparison goes through\n`WorkTier.MeetsRequirement`, never a raw `<` or `>=`. Substituting the natural-looking\n`capability >= required` fails four tests at once, and the way it fails is the point: a\n4B laptop model takes both the ultra-hard and the security/destructive work.\n\n### Declaring what the work is\n\n`Place` needs a `WorkClass`; routing produces a `Subject`. `ClassOf(Subject)` is the\njoint, and it is where an automatic placer is most tempted to cheat — it would be easy to\nlook at a tool name, decide \"grep is harmless\", and route it to a laptop. fak does not do\nthat. Judging what a call can destroy is `internal/adjudicator`'s reversibility question,\nit sits a tier above this leaf, and it is deliberately not re-guessed from a name here.\n\nSo a work class is **declared, or it is conservative**. Declare it as a subject label:\n\n```jsonc\n{ \"match\": { \"aspect\": \"tool_call\", \"labels\": { \"work_class\": \"routine\" } },\n \"plan\": { \"model\": \"tiny\" } }\n```\n\n| Input | Classified as | Why |\n| --- | --- | --- |\n| `labels.work_class` naming a known class | that class, **declared** | the operator said so |\n| `aspect: \"scout\"`, nothing declared | `routine`, **declared** | a scout *is* the cheap classify-first probe — the aspect names the work |\n| anything else | *(empty)*, **undeclared** | `PolicyFor` already puts the empty class at the strictest floor |\n| `labels.work_class` misspelled | *(empty)*, **undeclared** | a typo in `routine` must not read as permission to use a laptop |\n\nUndeclared work therefore lands exactly where it landed before this feature existed — on\na vendor — and reports `class-undeclared-conservative` so an operator can see the fix is\na label rather than more hardware. There is no second conservatism mechanism layered on\ntop; one gate in one place is why the behaviour stays predictable. `Complexity` can then\nratchet a floor **up** (a high-complexity `routine` subject becomes `normal-impl`) and\nnever down, which is what makes accepting complexity as an input safe at all.\n\n`Roster.PlaceSubject(subject, candidates)` composes the two and returns the placement\n*and* the classification, so both halves of the decision are visible together.\n\n### Declaring what a sub-agent type does\n\nSub-agents are where the cheap rungs would pay for themselves — a fleet spawns far more\ndelegated work than top-level turns — and `Roster.PlaceSpawn(parent, class, candidates)`\nalready answers \"which rung should this child run on\". It refuses an **empty** work class\non purpose: an unclassified spawn is a missing classification, not a routine one, and\nletting delegation reach a laptop without stating what the work *is* would be exactly the\nshape a floor bypass takes.\n\nThat refusal is what left the call unreachable. A spawn arrives as an admitted tool call\ncarrying an agent **type** and a prompt. The prompt is prose, and reading a class out of\nprose is the guess `ClassOf` refuses everywhere else. The type is structured — but it is\nnot a work class: only an operator knows whether the agent type *their* fleet calls\n`explore` does bounded lookup or ships code.\n\nSo it is declared, in the roster the operator already owns:\n\n```jsonc\n\"spawn_classes\": [\n { \"type\": \"explore\", \"class\": \"routine\" },\n { \"type\": \"code-reviewer\", \"class\": \"normal-impl\" },\n { \"type\": \"release-cutter\", \"class\": \"security-release-destructive\" }\n]\n```\n\n`Roster.SpawnClassFor(type)` resolves one, returning `(class, declared bool)`. The rules\nare the ones the rest of this leaf runs on:\n\n| Input | Resolves to | Why |\n| --- | --- | --- |\n| a declared type (case- and space-insensitive) | that class, **declared** | the operator said so |\n| a type nobody declared, or no `spawn_classes` block at all | *(empty)*, **undeclared** | `PlaceSpawn` then refuses — the spawn keeps whatever placement it has today |\n| a declared type whose class token this package does not know | *(empty)*, **undeclared** | same rule as a misspelled `work_class` label: a typo is not permission to use a laptop |\n\nThe match is **exact**, never a prefix and never a glob — `explore` must not answer for\n`explore-and-delete`. A malformed entry is refused at load rather than skipped: an empty\ntype, an empty or unknown class (the error names the four options), a duplicate type, or a\ntype carrying a route delimiter. A silently-dropped entry is the worst outcome available,\nbecause it looks correct from the operator's side while behaving like an undeclared one,\nand nothing points at the typo.\n\n**Shipped:** the declaration, its validation, and the resolver — plus the block in\n`examples/model-accounts.example.json`, which the test suite parses and resolves.\n**Not shipped:** nothing on the spawn path calls `SpawnClassFor` yet. The gateway does\nobserve spawns (`internal/gateway/adjudicate_proposed.go` stamps every admitted tool call\nand already recognises the spawn-shaped ones — that is where `spawn_count` comes from),\nand that is the seam a placement call would bind to, but binding it changes where\ndelegated traffic runs and is a separate, individually reviewable change under\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416). Declaring\n`spawn_classes` today moves no traffic.\n\nOne neighbouring field is *not* this signal, and it has already been mistaken for it: a\nsession's `ParentTrace`/`Generation` is **continuation** lineage — the same agent after a\nbudget-reset re-continuation — written only by `session.Table.Recontinue`. It never marks\na row as somebody's child. `SpawnCount` is the sub-agent axis, and it is parent-side: it\ncounts children a trace spawned, never whose child a trace is.\n\n### Seeing the ladder before it moves traffic: `fak route --place`\n\n```console\n$ fak route --accounts accounts.json --place --labels work_class=routine\n```\n\nThe oracle walks the ladder for one subject against your real roster and prints the rung\nit chose, every rung it passed over, and the closed reason token for each. It exists\n*before* anything on the dispatch path calls `Place`, deliberately: a placement policy\nnobody can inspect is a policy nobody can be held to, and an operator has to be able to\nsee what the ladder would do with **their** roster before fak starts moving traffic on\nthe strength of it. Add `--json` for the same answer as a report.\n\nThe candidate pool is every model the roster **binds**, not just the one routing picked —\nthe question is which rung can serve this class of work at all. Compatibility-only and\ndeprecated-alias bindings are excluded, since admitting a legacy spelling would let the\nsame hardware appear twice on a rung.\n\nAdd `--spawn-type TYPE` to ask the *delegated* question in the same breath: where would a\nsub-agent of that type run, given that this turn ran where the block above says. Against\nthe shipped example roster:\n\n```console\n$ fak route --accounts examples/model-accounts.example.json --place \\\n --labels work_class=ultra-hard --spawn-type explore \\\n --capability zone-device=t2,zone-fleet=t1,large=t0\n```\n```\nSPAWN PLACEMENT (--spawn-type explore)\n work class routine [declared by the roster's spawn_classes]\n parent zone=vendor model=deepseek-flash\n placed zone=device model=zone-device\n self-hosted yes escalated no failed-over no\n relation spawn-descended-from-parent-zone spawn-inherit-unmeasured\n descent yes self-hosted descent yes\n```\n\nThe child's class comes from the `spawn_classes` declaration above — never from the\nparent, the prompt, or the spelling of the type — so an **undeclared** type is refused\nwith the roster's own list of declared ones rather than assumed routine. The parent's rung\nis *recorded, not obeyed*: a sub-agent spawned from a frontier turn still lands on the\nlaptop, which is the whole point, and `self-hosted descent` is the event\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416) counts. The report also\nanswers the counterfactual — what *inheriting* the parent's model would have done — and\nsays `UNKNOWN` when the parent was never graded, because \"the status quo was fine here\"\nis a claim that needs a measurement behind it. `--json` carries the same answer under a\n`spawn` key that is **absent** when the question was not asked.\n\n### Saying what a model can do\n\nBecause unmeasured capability may not descend the ladder, a fresh roster places\n*everything* on the vendor rung — correctly, and uselessly. There are three ways to give\nthe ladder something to descend on, in increasing order of what they cost to produce:\n\n| Flag | What it is | What it costs to be wrong |\n| --- | --- | --- |\n| `--capability qwen3.6-4b=t2,glm-5.2=t1` | an operator **asserting** a grade | attributable to a person, but does not scale past a handful of models |\n| `--evidence FILE` | a summary of observed outcomes, graded here | someone assembled the counts; you are trusting their arithmetic |\n| `--outcomes FILE` | the append-only turn journal, **counted here** | nothing is trusted but the record itself |\n\nAll three feed one grader (`modelroute.GradeCapability`), and its bar is yours:\n\n```console\n$ fak route --accounts accounts.json --place --labels work_class=routine \\\n --outcomes turns.jsonl --since 30d --grade-floor attempts=50,rate=0.9,witness\n```\n\nThe default bar is 20 independently verified attempts at 80%. There is deliberately **no\nknob that makes self-report count** — a model's own claim of 500/500 successes buys\nnothing, and the refused attempts are reported *as refused* rather than as absent. An\noperator who wants to assert a capability can already do so, attributably, with\n`--capability`. A model claimed by both an assertion and a measurement is a named\nrefusal: they cannot both be the grade.\n\nA grade is the **floor** of the work observed, never its optimal tier, and a model that\nfails the bar is `UNMEASURED` **with a reason** rather than graded at the worst tier. The\nthree reasons call for opposite responses and are never collapsed into one:\n\n| Reason | The fix |\n| --- | --- |\n| `no-trusted-evidence` | run the turns under a witness or a judge — the volume is there, the provenance is not |\n| `insufficient-samples` | run more turns, or lower `attempts=` if you meant to |\n| `below-success-floor` | this model cannot do this work; that is the answer |\n\n### Grading from the turn journal\n\n`--outcomes` reads the record itself — one JSON object per line, appended as turns\nhappen (`modelroute.TurnOutcome`):\n\n```jsonl\n{\"id\":\"t-4471\",\"model\":\"qwen3.6-4b\",\"class\":\"routine\",\"zone\":\"device\",\"success\":true,\"verify\":\"witness\",\"at\":\"2026-07-24T11:02:00Z\"}\n```\n\nThe fold is written to **lose** evidence rather than invent it, because each alternative\ninflates a claim, and it reports every loss:\n\n```\n journal turns.jsonl: 1463 line(s), 1332 counted, 4 model(s) with evidence\n not counted 1 unparseable line(s), 30 replayed id(s), 60 older than 30d, 40 undated\n```\n\n- A repeated `id` is counted **once**. Replaying a file is the cheapest possible way to\n manufacture a grade: the same 20 successes appended three times must not read as 60.\n- An **undated** row cannot be shown to be inside a window, so `--since` excludes it — and\n counts it apart from a stale one, because one needs a wider window and the other needs a\n producer that stamps its rows. Ask for no window and a missing date costs nothing.\n- **Provenance is never merged.** 100 self-reported turns and 20 witnessed ones about the\n same model stay two rows; merging them would force a pick of provenance, and any pick\n is a lie. The grade keeps the weakest provenance behind whatever it merged.\n- A **torn line** is skipped and counted, never fatal. A fleet appending to this file will\n eventually die mid-write, and discarding thousands of verified turns over one partial\n row would make the honest path the fragile one.\n- Rows with no `id` are kept — a missing id is not proof of a duplicate — but reported as\n a corpus that **cannot be checked for replay**, since that is a fact about how much the\n number is worth.\n\n`--evidence` and `--outcomes` are mutually exclusive: a run that took some of its answer\nfrom a journal and some from a hand-written summary produces a grade whose provenance\nnobody can state afterwards.\n\n**What is not shipped:** nothing yet *writes* this journal. `internal/ablate`'s\n`StubTierScorer` still grades nothing, so today the file is something you produce — from\nDOS's git witness, a judge score, or your own harness. That producer is the one piece\nstanding between this ladder and traffic that moves by itself.\n\n### Placing against what is actually answering\n\nA grade says what a model *can* do. It says nothing about whether the box is powered on.\n`--serving FILE` hands the ladder a liveness snapshot — what your own probes just saw —\nand the ladder walks past candidates it reports are not serving:\n\n```json\n{\n \"schema\": \"fak.modelroute.serving.v1\",\n \"as_of_unix\": 1785312000,\n \"max_age_seconds\": 120,\n \"covers\": [\"device\", \"fleet\"],\n \"models\": {\n \"zone-device\": {\"state\": \"up\", \"observed_unix\": 1785311950},\n \"zone-fleet\": {\"state\": \"down\", \"observed_unix\": 1785311950},\n \"zone-fleet-agentic\": {\"state\": \"up\", \"observed_unix\": 1785311950}\n }\n}\n```\n\n```console\n$ fak route --accounts examples/model-accounts.example.json --place \\\n --labels work_class=normal-impl --serving serving.json \\\n --capability zone-device=t2,zone-fleet=t1,zone-fleet-agentic=t1,large=t0\n```\n```\n placed zone=fleet model=zone-fleet-agentic\n self-hosted yes escalated yes failed-over yes\n ladder\n device - zone-capability-unmeasured x2 zone-under-tier\n fleet zone-fleet-agentic zone-serving-down placed-in-zone escalated-past-cheaper-zone\n vendor - zone-not-reached\n\nSERVING SNAPSHOT (--serving serving.json)\n as of 1785312000 max age 120s\n covers device fleet\n observations 3, of which 3 name a model this roster binds\n SILENT small tiny-classifier\n the snapshot claims to speak for these candidates' rungs and then says\n nothing about them, so they are passed over as unknown rather than\n assumed well. That is a gap in the probe, not an outage.\n```\n\n**One dead GPU host is not a dead rung.** A fleet is several machines, and abandoning the\nrung because one box rebooted sends every token to a third-party lab to route around a\nneighbour that was idle — so the failover above stayed on the company's own hardware.\n\n| Verdict | What it means | Placed? |\n| --- | --- | --- |\n| `zone-serving-down` | observed unable to serve | passed over |\n| `zone-serving-unknown` | no verdict, on a rung the snapshot *claims* to cover | passed over |\n| `zone-serving-stale` | a verdict that cannot be shown fresh | passed over |\n| `zone-serving-degraded` | serving under strain | **recorded, and placed anyway** |\n\nDegraded is deliberately not actionable: a loaded host still takes work, and shedding at\nthe first sign of queueing inverts self-hosting exactly in the busy hour it is meant for.\n\nThree rules decide the rest, and each is fail-closed in a different direction:\n\n- **Silence only means something where you said it would.** `covers` is the operator\n declaring which rungs the probe actually watches; a candidate the snapshot is silent\n about *inside* that coverage is unknown, not healthy, because a crashed prober must not\n read as a fleet in perfect health. Outside coverage, silence gates nothing.\n- **An observation is always honored**, coverage or not. Coverage governs what silence\n means, never what a verdict means — a report that says a host is down is not overruled\n by a `covers` list that forgot to mention its rung.\n- **Freshness is fail-closed, and so is every way of not having it**: no observation\n stamp, no `as_of_unix`, past `max_age_seconds`, or stamped *after* the report all read\n as stale. A snapshot with a bound and no clock to measure against therefore gates\n everything it covers, which is correct and reads exactly like a total outage — so the\n summary says `STALE-ALL` and names the missing field rather than leaving you to guess.\n\n**`failed-over` is carried apart from `escalated`** because they want opposite fixes.\nEscalated says a cheaper rung could not do this work — a capability fact, stable until\nsomeone changes the roster. Failed-over says a rung that *could* have was routed around\nbecause something was not answering — an operations fact that may already be untrue. A\nbill read without that second bit shows vendor spend attributed to work that \"needed\" a\nvendor, and sends an operator hunting a capability problem that does not exist while the\ndead host stays dead. Both are printed for every placement, including the ones that stayed\ncheap: a failover *within* a rung cost nothing and still means something is down.\n\nThe snapshot and the roster are validated apart and meet only here, so the report also\nnames what neither half can see alone:\n\n```\n observations 2, of which 0 name a model this roster binds\n UNBOUND glm-5.2 kimi-k3\n these observations gate NOTHING: the ladder only ever asks about\n models the roster binds, so a probe filed under an id nothing binds\n is honored nowhere and this run is identical to one with no snapshot.\n Check the ids against the roster's bindings, not its upstream names.\n Each of these is the UPSTREAM name of a candidate: the probe filed its\n result under the name it dialled, and the ladder asks by routed id.\n glm-5.2 -> zone-fleet\n kimi-k3 -> zone-fleet-agentic\n```\n\nThat is the fail-open the flag would otherwise ship: a clean-validating report, an\noperator who believes they gated a dead host, and a placement identical to one with no\nsnapshot at all. The same typo *inside* declared coverage fails the other way instead —\nthe real candidate is now silent on a rung the report speaks for, so it is passed over as\nunknown and a healthy host looks dead. One id appearing in `UNBOUND` and its routed name\nin `SILENT` is the signature of exactly this mistake. Where an upstream name resolves to\n**two** candidates, both are named: two accounts serving the same weights is how a rung\nscales, and one observation cannot speak for both pools — which is why the snapshot is\nkeyed by routed id in the first place.\n\nTwo smaller refusals hold the same line. An unknown field is rejected rather than ignored,\nbecause `max_age_sec` otherwise parses, validates, looks right, and silently switches\nfail-closed freshness *off*. And a named file that cannot be read is an error, never an\nempty report: dropping the flag means \"no liveness signal\", while a missing file means\nyour probe did not run, and placing work as though every host were healthy is precisely\nthe failure the gate exists to prevent. In `--json` the `serving` key is **absent** unless\na snapshot was supplied, so it can never be misread as a probe that saw nothing.\n\n**What is not shipped:** nothing produces this file either. It is deliberately not a probe\nof fak's own — placement is asserted to be pure (same roster, same candidates, same\nreport, same answer), and a CLI that dialled endpoints would be reading a clock and a\nnetwork inside a decision that reads neither. The snapshot is your monitoring's output,\nin fak's vocabulary.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## Managed-cache posture across launchers\n\nEvery fak launcher that fronts an agent with `fak guard` — `fak accounts launch`,\n`fak codex`, and the dispatch worker (`fak dispatch tick`/`auto`) — can hand guard a\n**managed-cache posture** that decides whether the gateway upgrades the stable-prefix\n`cache_control` breakpoint to the **1h-TTL** tier on the outbound Anthropic wire.\n\n**A subscription seat stays passive by default, on purpose.** Guard's own `--managed-cache\nauto` follows a never-speculate rule: it will not manage a wire whose billing it cannot see.\nA Pro/Max seat authenticates with subscription OAuth (`Authorization: Bearer`), whose cache\neconomics guard does not own, so `auto` resolves **passive** there and no launcher silently\nflips it. Nothing changes for a subscription fleet unless you ask for it.\n\n**An API-key-billed fleet reaches ACTIVE without hand-editing any launcher**, via two env\nknobs read once per launch and threaded onto the child's guard argv (not its environment, so\na *resumed* child — whose gateway env is deliberately stripped — keeps the posture):\n\n| Knob | Effect |\n|------|--------|\n| `FAK_GUARD_API_KEY_ENV=ANTHROPIC_API_KEY` | Names the env var holding the API key. On the Anthropic wire this is the explicit opt-IN to API billing that lets `auto` resolve **ACTIVE** (guard bills the key, not the seat's subscription). |\n| `FAK_MANAGED_CACHE=on\\|off\\|auto` | Forces the posture. `on` upgrades regardless of billing; `off` disables; `auto` (default) defers to the billing signal above. |\n\n`fak accounts launch --managed-cache ` and `fak codex --managed-cache ` expose\nthe same lever per-launch (defaulting to `$FAK_MANAGED_CACHE`); the dispatch worker reads the\nenv knobs only. An unknown mode fails loud at the front-ends; the headless worker warns and\nfalls back to `auto` so a fleet turn never dies over a cache-posture typo. Either way the\nguard child prints the RESOLVED posture in its startup banner, so the worker log witnesses\nwhether the 1h upgrade actually engaged. An unconfigured fleet emits no `--managed-cache`\nflag at all — the guard argv is byte-identical to before this knob existed.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch\n([#2528](https://github.com/anthony-chaudhary/fak/issues/2528)). v1 *resolves and prints*\na binding; it does not yet send a request over the wire to your chosen account. Building\nthe planners from the Targets and running an ensemble's members is the additive dispatch\nwiring, the same shipped-vs-deferred split the routing spine uses for the ensemble fold\nversus its execution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n\n**Placement zones — shipped and deferred, precisely.** *Shipped:* the vocabulary. A\ncompany-operated server is expressible (`kind: fleet`), its account validates on its own\nterms, its route string round-trips to `ZoneFleet`, and `PlacementZone.SelfHosted()`\ngives every downstream consumer one predicate to attribute a token by. Witnessed by\n`TestZonesPartitionTheKnownKinds`, `TestEngineRouteCarriesTheZoneBackOut`,\n`TestShippedExampleRosterCarriesAllThreeZones`, and — at the enforcing floor —\n`TestFleetZoneStaysRemoteAtTheFloor` plus `TestTierOneRouteMirrorAgreesWithTheFloor`.\n*Also shipped:* `Roster.Place` **chooses** a zone (see *Walking the ladder* above).\n*Deferred, and deliberately so:* nothing yet calls it on the live dispatch path, the\nserving path does not yet **record** the zone it used, and the residency floor does not\nyet treat a fleet host as inside a trust boundary. Zone-aware placement, the self-hosted\ntoken-fraction metric, and the escalation ladder are separate, individually reviewable\nchanges on top of this vocabulary — not implied by it. They are tracked as\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416).\n\n### The first consumer: honest stratum attribution\n\n`internal/sessionaudit` classifies audited turns into billing buckets. It used to answer\n\"was this self-hosted?\" from the **model id**, which cannot support the claim: Qwen,\nLlama, Mistral, DeepSeek, GLM and Kimi all publish open weights, so each is servable\neither from hardware you operate or from a vendor's API. That produced two contradictions\nin one file — `deepseek*` was bucketed self-hosted while priced from the published\nDeepSeek *API* rate card, and the roster above binds `qwen/qwen3.6-27b` to **Groq**, a\nvendor, while the classifier called those tokens self-hosted.\n\nA model id now buys only a model-family answer (`open-weights (placement unknown)`), and\nplacement comes from a real signal:\n\n| Call | Answers |\n| --- | --- |\n| `ProviderBucket(model)` | whose *model* it is — never where it ran |\n| `BucketForPlacement(model, zone)` | the bucket, given a `PlacementZone` string |\n| `BucketIsSelfHosted(bucket)` | `(selfHosted, known bool)` — two values on purpose |\n\n`BucketIsSelfHosted` returns a second `known` bool so unattributable volume cannot be\nfolded into either side of a self-hosted fraction. `TestPricedModelsAreNeverBucketedSelfHosted`\nmakes the underlying rule executable and total over the rate card: a model billed per token\nby a third party ran on their hardware, so it can never also be bucketed as self-hosted.\n"}, "3e250c15b50feda241448184eb6ee66cbb1f4944": {"t": 1785749569.3876252, "n": 36377, "h": "80cf603ba487ec5f", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, DeepSeek, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, Groq, DeepSeek, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product sibling of the fleet account switcher (`fak fleet-accounts`,\nthe native Go successor to the legacy `tools/fleet_accounts.py` shim): provider-neutral,\ncredential-safe, and composable with the routing spine. It lives in\n`internal/modelroute/account.go` (pure, stdlib-only, the same package as the routing\ndecision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\nOpenAI-compatible providers such as Groq are represented as `kind: \"openai\"` plus their\nprovider `base_url`, so the wire adapter stays generic while the account id names whose\ncredential is being used.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" },\n { \"id\": \"july6netra_groq\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 1000,\n \"tokens_per_minute\": 8000, \"tokens_per_day\": 200000 },\n { \"id\": \"july6netra_groq_compound\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 250 },\n { \"id\": \"deepseek\", \"kind\": \"deepseek\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 },\n { \"id\": \"deepseek-anthropic\", \"kind\": \"anthropic\",\n \"base_url\": \"https://api.deepseek.com/anthropic\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" },\n { \"model\": \"qwen36-groq\", \"account\": \"july6netra_groq\", \"upstream_model\": \"qwen/qwen3.6-27b\" },\n { \"model\": \"groq-compound\", \"account\": \"july6netra_groq_compound\", \"upstream_model\": \"groq/compound\" },\n { \"model\": \"deepseek-pro\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-pro\" },\n { \"model\": \"deepseek-flash\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-flash\" },\n { \"model\": \"deepseek-chat-compat\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-chat\",\n \"compatibility_only\": true, \"deprecated_after_utc\": \"2026-07-24 15:59 UTC\",\n \"deprecated_alias_for\": \"deepseek-v4-flash non-thinking mode\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nCoding-agent subscriptions add a second layer: **config-home accounts**. Claude uses\n`CLAUDE_CONFIG_DIR` homes such as `~/.claude-gem8-seat`; Codex uses `CODEX_HOME` homes such as\n`~/.codex` or `~/.codex-work`. `fak fleet-accounts` discovers both, derives their non-secret\nprovider-account identity, collapses duplicate homes on the same rate-limit bucket, and offers\nonly homes whose credential state is ready. The default Codex picker profile is\n`gpt-5.6-sol` with `model_reasoning_effort=xhigh`.\n\nThe authored lifecycle registry behind `fak accounts` remains Claude-specific today; its seat\nnames and tombstones are therefore applied only to Claude rows. Codex rows take lifecycle/login\ntruth from their own home (`auth.json`/`config.toml`) and never inherit a same-named Claude seat\nsuch as `default`.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## API-host probe bridge\n\nThe same model-account roster can feed the API-host no-spend probes:\n\n```bash\nfak api-host readiness --from-model-accounts examples/model-accounts.example.json\nfak api-host acceptance --from-model-accounts examples/model-accounts.example.json\n```\n\n`readiness` converts only OpenAI-compatible or local accounts into `/models` probe targets\n(`openai`, `openai-responses`, `xai`, `deepseek`, and `local`). Native provider accounts are not guessed into\nan OpenAI-shaped probe. `acceptance` keeps every account with a probeable base URL visible:\nOpenAI-compatible/local accounts can become `READY_FOR_LIVE_BRIDGE_RUN`, while native\nAnthropic/Gemini accounts are reported as `WIRE_SUPPORTED_UNPROBED`. Model hints come from the\nroster bindings, so the API-host report stays tied to the same abstract model ids the route policy\nuses.\n\nGroq Qwen3.6 uses the OpenAI-compatible base `https://api.groq.com/openai/v1`, credential\nenv var `FAK_GROQ_API_KEY`, and upstream model slug `qwen/qwen3.6-27b`. The example records\nthe current account/model limits as metadata: 30 requests/minute, 1,000 requests/day,\n8,000 tokens/minute, and 200,000 tokens/day. Groq Compound uses the same base and env var\nunder the separate account id `july6netra_groq_compound`, bound as the lower-quality\n`groq-compound` target to upstream slug `groq/compound`; its metadata is request-only:\n30 requests/minute and 250 requests/day, with no token-minute or token-day cap recorded.\nThe Claude dogfood launcher separately caps the outbound `max_tokens` field at 8192 for\n`groq/compound`, because Groq rejects larger per-response output budgets. Those values\nare advisory metadata for routing and launchers; the v1 resolver does not throttle\nrequests.\n\nDeepSeek V4 uses `deepseek-v4-pro` / `deepseek-v4-flash` on the OpenAI-compatible base\n`https://api.deepseek.com`; its Anthropic-compatible profile uses\n`https://api.deepseek.com/anthropic`. The legacy `deepseek-chat` and `deepseek-reasoner`\naliases are accepted only when marked `compatibility_only`, with the documented retirement\nmetadata `2026-07-24 15:59 UTC`, so a new roster does not silently bind to an alias that is\nabout to disappear.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open. The same file pins the tier-1 mirror\n(`modelroute.IsRemoteRoute`) against the enforcing floor over one corpus, because\n`internal/modelroute` sits below `internal/engine` and must keep its own copy of the\non-box family list. See *Placement zones* below for the third rung the binary\nlocal/remote split does not name.\n\n## Placement zones: your box, your company's boxes, someone else's\n\nLocality is not binary. A company that self-hosts runs models in **three** places, and\nan account declares which by its `kind`:\n\n| Zone | `kind` | Where the weights sit | Self-hosted? | On-box? |\n| --- | --- | --- | --- | --- |\n| `device` | `local` | the engineer's own machine (loopback only) | yes | **yes** |\n| `fleet` | `fleet` | a machine the **organization** operates | yes | no |\n| `vendor` | `openai` / `anthropic` / `gemini` / `xai` / `deepseek` / `openai-responses` | a third-party lab's API | no | no |\n\nThe middle rung is the one that carries the token volume in a self-hosting shop — a\nGLM- or Kimi-class open model on company GPUs, shared by every engineer. Before\n`kind: fleet` existed it could not be written down: as `local` it was refused (a local\naccount must carry a loopback base URL), and as `openai` it was admitted but became\nindistinguishable from `api.openai.com` in every downstream record, so the org got no\ncredit for hardware it owns.\n\n```json\n{\n \"id\": \"corp-glm\",\n \"kind\": \"fleet\",\n \"base_url\": \"http://glm.infer.corp.internal:8000/v1\",\n \"label\": \"GLM-5.2 vLLM server the company operates\"\n}\n```\n\nA `fleet` account needs an explicit, non-loopback `http(s)` endpoint (there is no public\ndefault for a host only your org can name, and a loopback address is `device` by\ndefinition — the zones must stay disjoint). Unlike a vendor account it does **not**\nrequire a `cred_env`: an org-operated server on a private network commonly has no API\nkey. Supply one when the endpoint authenticates.\n\n**Self-hosted is not the same as on-box, deliberately.** `ZoneFleet.SelfHosted()` is\ntrue — its tokens count toward \"we ran this on our own silicon\" — but\n`ZoneFleet.OnBox()` is false, so the residency floor still treats a `fleet:…` route as\nremote and still denies a tenant-scoped payload routed there. Naming the zone changes\nwhat fak can *attribute*, not what it *permits*. Letting an org-operated host carry a\nsensitive payload is an enforcement change that needs an operator-declared trust\nboundary (authenticated transport, a named host allowlist), and it is tracked\nseparately — it does not arrive as a side effect of declaring a zone.\n\nThe zones are ordered as an escalation ladder — `device` (0) → `fleet` (1) →\n`vendor` (2) — so \"prefer the cheapest rung that can do the work\" is a comparison on a\ndeclared value rather than a string switch. An unrecognized zone ranks *above* vendor\nand is neither self-hosted nor on-box: unattributable never reads as free or as safe.\n\n### Walking the ladder: `Roster.Place`\n\n`Roster.Place(class, candidates)` is what actually walks it. Given a `WorkClass` and the\nmodels you have bound — each with the work tier you have evidence it can serve — it\nreturns the cheapest rung that can take the job, plus the full ladder walk that produced\nthe answer:\n\n```go\np, err := roster.Place(modelroute.ClassRoutine, []modelroute.Candidate{\n {Model: \"tiny\", Capability: modelroute.TierT2, Measured: true},\n {Model: \"corp-mid\", Capability: modelroute.TierT1, Measured: true},\n {Model: \"frontier\", Capability: modelroute.TierT0, Measured: true},\n})\n// p.Zone == ZoneDevice, p.SelfHosted() == true, p.Escalated == false\n```\n\nTwo rules stop it from being wishful thinking:\n\n**The floor belongs to the work, not to the model.** Admission runs through\n`TierPolicy.Admit`, so a cheap rung cannot take work above its tier however available it\nis. Security/release/destructive work has a floor that never drops to routine, so it\nskips a measured local model and lands on the fleet — `p.Escalated` is true and the\ndevice rung reports `zone-under-tier`. An unrecognized class stays at the strictest floor\nrather than inferring a cheap one.\n\n**Unmeasured capability may not descend the ladder.** `Candidate.Measured` records\nwhether `Capability` is a measurement or an assertion, and an assertion is skipped on\nevery rung below the vendor. This matters right now: `internal/ablate`'s `StubTierScorer`\nmeasures nothing, so a placer that trusted asserted grades would route everything to the\ncheapest rung on a number nobody computed, and would look like it was working. Until\ncapability measurement lands, an operator opts a rung in by marking a candidate\n`Measured` — a claim they can be held to. `TestUnmeasuredCapabilityCannotDescendTheLadder`\npins both halves: an all-asserted ladder places on the vendor, and the same work moves to\nthe device the moment the cheap rung is measured.\n\nEvery rung is reported, including the ones passed over, in a closed reason vocabulary\n(`placed-in-zone`, `zone-under-tier`, `zone-capability-unmeasured`, `zone-no-candidate`,\n`zone-not-reached`, `escalated-past-cheaper-zone`) — because \"why is this *still* going to\na vendor?\" is the question an operator actually asks. A candidate the roster cannot\nresolve is a loud error, never a silent skip: a typo in a placement config must surface as\na misconfiguration rather than as traffic quietly continuing to bill a vendor.\n\nThe trap this code is written around is that `WorkTier` numbers are **inverted** — T0 is\nthe most demanding but the lowest number. Every comparison goes through\n`WorkTier.MeetsRequirement`, never a raw `<` or `>=`. Substituting the natural-looking\n`capability >= required` fails four tests at once, and the way it fails is the point: a\n4B laptop model takes both the ultra-hard and the security/destructive work.\n\n### Declaring what the work is\n\n`Place` needs a `WorkClass`; routing produces a `Subject`. `ClassOf(Subject)` is the\njoint, and it is where an automatic placer is most tempted to cheat — it would be easy to\nlook at a tool name, decide \"grep is harmless\", and route it to a laptop. fak does not do\nthat. Judging what a call can destroy is `internal/adjudicator`'s reversibility question,\nit sits a tier above this leaf, and it is deliberately not re-guessed from a name here.\n\nSo a work class is **declared, or it is conservative**. Declare it as a subject label:\n\n```jsonc\n{ \"match\": { \"aspect\": \"tool_call\", \"labels\": { \"work_class\": \"routine\" } },\n \"plan\": { \"model\": \"tiny\" } }\n```\n\n| Input | Classified as | Why |\n| --- | --- | --- |\n| `labels.work_class` naming a known class | that class, **declared** | the operator said so |\n| `aspect: \"scout\"`, nothing declared | `routine`, **declared** | a scout *is* the cheap classify-first probe — the aspect names the work |\n| anything else | *(empty)*, **undeclared** | `PolicyFor` already puts the empty class at the strictest floor |\n| `labels.work_class` misspelled | *(empty)*, **undeclared** | a typo in `routine` must not read as permission to use a laptop |\n\nUndeclared work therefore lands exactly where it landed before this feature existed — on\na vendor — and reports `class-undeclared-conservative` so an operator can see the fix is\na label rather than more hardware. There is no second conservatism mechanism layered on\ntop; one gate in one place is why the behaviour stays predictable. `Complexity` can then\nratchet a floor **up** (a high-complexity `routine` subject becomes `normal-impl`) and\nnever down, which is what makes accepting complexity as an input safe at all.\n\n`Roster.PlaceSubject(subject, candidates)` composes the two and returns the placement\n*and* the classification, so both halves of the decision are visible together.\n\n### Declaring what a sub-agent type does\n\nSub-agents are where the cheap rungs would pay for themselves — a fleet spawns far more\ndelegated work than top-level turns — and `Roster.PlaceSpawn(parent, class, candidates)`\nalready answers \"which rung should this child run on\". It refuses an **empty** work class\non purpose: an unclassified spawn is a missing classification, not a routine one, and\nletting delegation reach a laptop without stating what the work *is* would be exactly the\nshape a floor bypass takes.\n\nThat refusal is what left the call unreachable. A spawn arrives as an admitted tool call\ncarrying an agent **type** and a prompt. The prompt is prose, and reading a class out of\nprose is the guess `ClassOf` refuses everywhere else. The type is structured — but it is\nnot a work class: only an operator knows whether the agent type *their* fleet calls\n`explore` does bounded lookup or ships code.\n\nSo it is declared, in the roster the operator already owns:\n\n```jsonc\n\"spawn_classes\": [\n { \"type\": \"explore\", \"class\": \"routine\" },\n { \"type\": \"code-reviewer\", \"class\": \"normal-impl\" },\n { \"type\": \"release-cutter\", \"class\": \"security-release-destructive\" }\n]\n```\n\n`Roster.SpawnClassFor(type)` resolves one, returning `(class, declared bool)`. The rules\nare the ones the rest of this leaf runs on:\n\n| Input | Resolves to | Why |\n| --- | --- | --- |\n| a declared type (case- and space-insensitive) | that class, **declared** | the operator said so |\n| a type nobody declared, or no `spawn_classes` block at all | *(empty)*, **undeclared** | `PlaceSpawn` then refuses — the spawn keeps whatever placement it has today |\n| a declared type whose class token this package does not know | *(empty)*, **undeclared** | same rule as a misspelled `work_class` label: a typo is not permission to use a laptop |\n\nThe match is **exact**, never a prefix and never a glob — `explore` must not answer for\n`explore-and-delete`. A malformed entry is refused at load rather than skipped: an empty\ntype, an empty or unknown class (the error names the four options), a duplicate type, or a\ntype carrying a route delimiter. A silently-dropped entry is the worst outcome available,\nbecause it looks correct from the operator's side while behaving like an undeclared one,\nand nothing points at the typo.\n\n**Shipped:** the declaration, its validation, and the resolver — plus the block in\n`examples/model-accounts.example.json`, which the test suite parses and resolves.\n**Not shipped:** nothing on the spawn path calls `SpawnClassFor` yet. The gateway does\nobserve spawns (`internal/gateway/adjudicate_proposed.go` stamps every admitted tool call\nand already recognises the spawn-shaped ones — that is where `spawn_count` comes from),\nand that is the seam a placement call would bind to, but binding it changes where\ndelegated traffic runs and is a separate, individually reviewable change under\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416). Declaring\n`spawn_classes` today moves no traffic.\n\nOne neighbouring field is *not* this signal, and it has already been mistaken for it: a\nsession's `ParentTrace`/`Generation` is **continuation** lineage — the same agent after a\nbudget-reset re-continuation — written only by `session.Table.Recontinue`. It never marks\na row as somebody's child. `SpawnCount` is the sub-agent axis, and it is parent-side: it\ncounts children a trace spawned, never whose child a trace is.\n\n### Seeing the ladder before it moves traffic: `fak route --place`\n\n```console\n$ fak route --accounts accounts.json --place --labels work_class=routine\n```\n\nThe oracle walks the ladder for one subject against your real roster and prints the rung\nit chose, every rung it passed over, and the closed reason token for each. It exists\n*before* anything on the dispatch path calls `Place`, deliberately: a placement policy\nnobody can inspect is a policy nobody can be held to, and an operator has to be able to\nsee what the ladder would do with **their** roster before fak starts moving traffic on\nthe strength of it. Add `--json` for the same answer as a report.\n\nThe candidate pool is every model the roster **binds**, not just the one routing picked —\nthe question is which rung can serve this class of work at all. Compatibility-only and\ndeprecated-alias bindings are excluded, since admitting a legacy spelling would let the\nsame hardware appear twice on a rung.\n\nAdd `--spawn-type TYPE` to ask the *delegated* question in the same breath: where would a\nsub-agent of that type run, given that this turn ran where the block above says. Against\nthe shipped example roster:\n\n```console\n$ fak route --accounts examples/model-accounts.example.json --place \\\n --labels work_class=ultra-hard --spawn-type explore \\\n --capability zone-device=t2,zone-fleet=t1,large=t0\n```\n```\nSPAWN PLACEMENT (--spawn-type explore)\n work class routine [declared by the roster's spawn_classes]\n parent zone=vendor model=deepseek-flash\n placed zone=device model=zone-device\n self-hosted yes escalated no\n relation spawn-descended-from-parent-zone spawn-inherit-unmeasured\n descent yes self-hosted descent yes\n```\n\nThe child's class comes from the `spawn_classes` declaration above — never from the\nparent, the prompt, or the spelling of the type — so an **undeclared** type is refused\nwith the roster's own list of declared ones rather than assumed routine. The parent's rung\nis *recorded, not obeyed*: a sub-agent spawned from a frontier turn still lands on the\nlaptop, which is the whole point, and `self-hosted descent` is the event\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416) counts. The report also\nanswers the counterfactual — what *inheriting* the parent's model would have done — and\nsays `UNKNOWN` when the parent was never graded, because \"the status quo was fine here\"\nis a claim that needs a measurement behind it. `--json` carries the same answer under a\n`spawn` key that is **absent** when the question was not asked.\n\n### Saying what a model can do\n\nBecause unmeasured capability may not descend the ladder, a fresh roster places\n*everything* on the vendor rung — correctly, and uselessly. There are three ways to give\nthe ladder something to descend on, in increasing order of what they cost to produce:\n\n| Flag | What it is | What it costs to be wrong |\n| --- | --- | --- |\n| `--capability qwen3.6-4b=t2,glm-5.2=t1` | an operator **asserting** a grade | attributable to a person, but does not scale past a handful of models |\n| `--evidence FILE` | a summary of observed outcomes, graded here | someone assembled the counts; you are trusting their arithmetic |\n| `--outcomes FILE` | the append-only turn journal, **counted here** | nothing is trusted but the record itself |\n\nAll three feed one grader (`modelroute.GradeCapability`), and its bar is yours:\n\n```console\n$ fak route --accounts accounts.json --place --labels work_class=routine \\\n --outcomes turns.jsonl --since 30d --grade-floor attempts=50,rate=0.9,witness\n```\n\nThe default bar is 20 independently verified attempts at 80%. There is deliberately **no\nknob that makes self-report count** — a model's own claim of 500/500 successes buys\nnothing, and the refused attempts are reported *as refused* rather than as absent. An\noperator who wants to assert a capability can already do so, attributably, with\n`--capability`. A model claimed by both an assertion and a measurement is a named\nrefusal: they cannot both be the grade.\n\nA grade is the **floor** of the work observed, never its optimal tier, and a model that\nfails the bar is `UNMEASURED` **with a reason** rather than graded at the worst tier. The\nthree reasons call for opposite responses and are never collapsed into one:\n\n| Reason | The fix |\n| --- | --- |\n| `no-trusted-evidence` | run the turns under a witness or a judge — the volume is there, the provenance is not |\n| `insufficient-samples` | run more turns, or lower `attempts=` if you meant to |\n| `below-success-floor` | this model cannot do this work; that is the answer |\n\n### Grading from the turn journal\n\n`--outcomes` reads the record itself — one JSON object per line, appended as turns\nhappen (`modelroute.TurnOutcome`):\n\n```jsonl\n{\"id\":\"t-4471\",\"model\":\"qwen3.6-4b\",\"class\":\"routine\",\"zone\":\"device\",\"success\":true,\"verify\":\"witness\",\"at\":\"2026-07-24T11:02:00Z\"}\n```\n\nThe fold is written to **lose** evidence rather than invent it, because each alternative\ninflates a claim, and it reports every loss:\n\n```\n journal turns.jsonl: 1463 line(s), 1332 counted, 4 model(s) with evidence\n not counted 1 unparseable line(s), 30 replayed id(s), 60 older than 30d, 40 undated\n```\n\n- A repeated `id` is counted **once**. Replaying a file is the cheapest possible way to\n manufacture a grade: the same 20 successes appended three times must not read as 60.\n- An **undated** row cannot be shown to be inside a window, so `--since` excludes it — and\n counts it apart from a stale one, because one needs a wider window and the other needs a\n producer that stamps its rows. Ask for no window and a missing date costs nothing.\n- **Provenance is never merged.** 100 self-reported turns and 20 witnessed ones about the\n same model stay two rows; merging them would force a pick of provenance, and any pick\n is a lie. The grade keeps the weakest provenance behind whatever it merged.\n- A **torn line** is skipped and counted, never fatal. A fleet appending to this file will\n eventually die mid-write, and discarding thousands of verified turns over one partial\n row would make the honest path the fragile one.\n- Rows with no `id` are kept — a missing id is not proof of a duplicate — but reported as\n a corpus that **cannot be checked for replay**, since that is a fact about how much the\n number is worth.\n\n`--evidence` and `--outcomes` are mutually exclusive: a run that took some of its answer\nfrom a journal and some from a hand-written summary produces a grade whose provenance\nnobody can state afterwards.\n\n**What is not shipped:** nothing yet *writes* this journal. `internal/ablate`'s\n`StubTierScorer` still grades nothing, so today the file is something you produce — from\nDOS's git witness, a judge score, or your own harness. That producer is the one piece\nstanding between this ladder and traffic that moves by itself.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## Managed-cache posture across launchers\n\nEvery fak launcher that fronts an agent with `fak guard` — `fak accounts launch`,\n`fak codex`, and the dispatch worker (`fak dispatch tick`/`auto`) — can hand guard a\n**managed-cache posture** that decides whether the gateway upgrades the stable-prefix\n`cache_control` breakpoint to the **1h-TTL** tier on the outbound Anthropic wire.\n\n**A subscription seat stays passive by default, on purpose.** Guard's own `--managed-cache\nauto` follows a never-speculate rule: it will not manage a wire whose billing it cannot see.\nA Pro/Max seat authenticates with subscription OAuth (`Authorization: Bearer`), whose cache\neconomics guard does not own, so `auto` resolves **passive** there and no launcher silently\nflips it. Nothing changes for a subscription fleet unless you ask for it.\n\n**An API-key-billed fleet reaches ACTIVE without hand-editing any launcher**, via two env\nknobs read once per launch and threaded onto the child's guard argv (not its environment, so\na *resumed* child — whose gateway env is deliberately stripped — keeps the posture):\n\n| Knob | Effect |\n|------|--------|\n| `FAK_GUARD_API_KEY_ENV=ANTHROPIC_API_KEY` | Names the env var holding the API key. On the Anthropic wire this is the explicit opt-IN to API billing that lets `auto` resolve **ACTIVE** (guard bills the key, not the seat's subscription). |\n| `FAK_MANAGED_CACHE=on\\|off\\|auto` | Forces the posture. `on` upgrades regardless of billing; `off` disables; `auto` (default) defers to the billing signal above. |\n\n`fak accounts launch --managed-cache ` and `fak codex --managed-cache ` expose\nthe same lever per-launch (defaulting to `$FAK_MANAGED_CACHE`); the dispatch worker reads the\nenv knobs only. An unknown mode fails loud at the front-ends; the headless worker warns and\nfalls back to `auto` so a fleet turn never dies over a cache-posture typo. Either way the\nguard child prints the RESOLVED posture in its startup banner, so the worker log witnesses\nwhether the 1h upgrade actually engaged. An unconfigured fleet emits no `--managed-cache`\nflag at all — the guard argv is byte-identical to before this knob existed.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch\n([#2528](https://github.com/anthony-chaudhary/fak/issues/2528)). v1 *resolves and prints*\na binding; it does not yet send a request over the wire to your chosen account. Building\nthe planners from the Targets and running an ensemble's members is the additive dispatch\nwiring, the same shipped-vs-deferred split the routing spine uses for the ensemble fold\nversus its execution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n\n**Placement zones — shipped and deferred, precisely.** *Shipped:* the vocabulary. A\ncompany-operated server is expressible (`kind: fleet`), its account validates on its own\nterms, its route string round-trips to `ZoneFleet`, and `PlacementZone.SelfHosted()`\ngives every downstream consumer one predicate to attribute a token by. Witnessed by\n`TestZonesPartitionTheKnownKinds`, `TestEngineRouteCarriesTheZoneBackOut`,\n`TestShippedExampleRosterCarriesAllThreeZones`, and — at the enforcing floor —\n`TestFleetZoneStaysRemoteAtTheFloor` plus `TestTierOneRouteMirrorAgreesWithTheFloor`.\n*Also shipped:* `Roster.Place` **chooses** a zone (see *Walking the ladder* above).\n*Deferred, and deliberately so:* nothing yet calls it on the live dispatch path, the\nserving path does not yet **record** the zone it used, and the residency floor does not\nyet treat a fleet host as inside a trust boundary. Zone-aware placement, the self-hosted\ntoken-fraction metric, and the escalation ladder are separate, individually reviewable\nchanges on top of this vocabulary — not implied by it. They are tracked as\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416).\n\n### The first consumer: honest stratum attribution\n\n`internal/sessionaudit` classifies audited turns into billing buckets. It used to answer\n\"was this self-hosted?\" from the **model id**, which cannot support the claim: Qwen,\nLlama, Mistral, DeepSeek, GLM and Kimi all publish open weights, so each is servable\neither from hardware you operate or from a vendor's API. That produced two contradictions\nin one file — `deepseek*` was bucketed self-hosted while priced from the published\nDeepSeek *API* rate card, and the roster above binds `qwen/qwen3.6-27b` to **Groq**, a\nvendor, while the classifier called those tokens self-hosted.\n\nA model id now buys only a model-family answer (`open-weights (placement unknown)`), and\nplacement comes from a real signal:\n\n| Call | Answers |\n| --- | --- |\n| `ProviderBucket(model)` | whose *model* it is — never where it ran |\n| `BucketForPlacement(model, zone)` | the bucket, given a `PlacementZone` string |\n| `BucketIsSelfHosted(bucket)` | `(selfHosted, known bool)` — two values on purpose |\n\n`BucketIsSelfHosted` returns a second `known` bool so unattributable volume cannot be\nfolded into either side of a self-hosted fraction. `TestPricedModelsAreNeverBucketedSelfHosted`\nmakes the underlying rule executable and total over the rate card: a model billed per token\nby a third party ran on their hardware, so it can never also be bucketed as self-hosted.\n"}, "e3ddd86efeb9ec5f78c56824d77ef100d8ca625d": {"t": 1785749570.6595063, "n": 34756, "h": "7e5f8767d680fb17", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, DeepSeek, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, Groq, DeepSeek, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product sibling of the fleet account switcher (`fak fleet-accounts`,\nthe native Go successor to the legacy `tools/fleet_accounts.py` shim): provider-neutral,\ncredential-safe, and composable with the routing spine. It lives in\n`internal/modelroute/account.go` (pure, stdlib-only, the same package as the routing\ndecision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\nOpenAI-compatible providers such as Groq are represented as `kind: \"openai\"` plus their\nprovider `base_url`, so the wire adapter stays generic while the account id names whose\ncredential is being used.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" },\n { \"id\": \"july6netra_groq\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 1000,\n \"tokens_per_minute\": 8000, \"tokens_per_day\": 200000 },\n { \"id\": \"july6netra_groq_compound\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 250 },\n { \"id\": \"deepseek\", \"kind\": \"deepseek\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 },\n { \"id\": \"deepseek-anthropic\", \"kind\": \"anthropic\",\n \"base_url\": \"https://api.deepseek.com/anthropic\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" },\n { \"model\": \"qwen36-groq\", \"account\": \"july6netra_groq\", \"upstream_model\": \"qwen/qwen3.6-27b\" },\n { \"model\": \"groq-compound\", \"account\": \"july6netra_groq_compound\", \"upstream_model\": \"groq/compound\" },\n { \"model\": \"deepseek-pro\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-pro\" },\n { \"model\": \"deepseek-flash\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-flash\" },\n { \"model\": \"deepseek-chat-compat\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-chat\",\n \"compatibility_only\": true, \"deprecated_after_utc\": \"2026-07-24 15:59 UTC\",\n \"deprecated_alias_for\": \"deepseek-v4-flash non-thinking mode\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nCoding-agent subscriptions add a second layer: **config-home accounts**. Claude uses\n`CLAUDE_CONFIG_DIR` homes such as `~/.claude-gem8-seat`; Codex uses `CODEX_HOME` homes such as\n`~/.codex` or `~/.codex-work`. `fak fleet-accounts` discovers both, derives their non-secret\nprovider-account identity, collapses duplicate homes on the same rate-limit bucket, and offers\nonly homes whose credential state is ready. The default Codex picker profile is\n`gpt-5.6-sol` with `model_reasoning_effort=xhigh`.\n\nThe authored lifecycle registry behind `fak accounts` remains Claude-specific today; its seat\nnames and tombstones are therefore applied only to Claude rows. Codex rows take lifecycle/login\ntruth from their own home (`auth.json`/`config.toml`) and never inherit a same-named Claude seat\nsuch as `default`.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## API-host probe bridge\n\nThe same model-account roster can feed the API-host no-spend probes:\n\n```bash\nfak api-host readiness --from-model-accounts examples/model-accounts.example.json\nfak api-host acceptance --from-model-accounts examples/model-accounts.example.json\n```\n\n`readiness` converts only OpenAI-compatible or local accounts into `/models` probe targets\n(`openai`, `openai-responses`, `xai`, `deepseek`, and `local`). Native provider accounts are not guessed into\nan OpenAI-shaped probe. `acceptance` keeps every account with a probeable base URL visible:\nOpenAI-compatible/local accounts can become `READY_FOR_LIVE_BRIDGE_RUN`, while native\nAnthropic/Gemini accounts are reported as `WIRE_SUPPORTED_UNPROBED`. Model hints come from the\nroster bindings, so the API-host report stays tied to the same abstract model ids the route policy\nuses.\n\nGroq Qwen3.6 uses the OpenAI-compatible base `https://api.groq.com/openai/v1`, credential\nenv var `FAK_GROQ_API_KEY`, and upstream model slug `qwen/qwen3.6-27b`. The example records\nthe current account/model limits as metadata: 30 requests/minute, 1,000 requests/day,\n8,000 tokens/minute, and 200,000 tokens/day. Groq Compound uses the same base and env var\nunder the separate account id `july6netra_groq_compound`, bound as the lower-quality\n`groq-compound` target to upstream slug `groq/compound`; its metadata is request-only:\n30 requests/minute and 250 requests/day, with no token-minute or token-day cap recorded.\nThe Claude dogfood launcher separately caps the outbound `max_tokens` field at 8192 for\n`groq/compound`, because Groq rejects larger per-response output budgets. Those values\nare advisory metadata for routing and launchers; the v1 resolver does not throttle\nrequests.\n\nDeepSeek V4 uses `deepseek-v4-pro` / `deepseek-v4-flash` on the OpenAI-compatible base\n`https://api.deepseek.com`; its Anthropic-compatible profile uses\n`https://api.deepseek.com/anthropic`. The legacy `deepseek-chat` and `deepseek-reasoner`\naliases are accepted only when marked `compatibility_only`, with the documented retirement\nmetadata `2026-07-24 15:59 UTC`, so a new roster does not silently bind to an alias that is\nabout to disappear.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open. The same file pins the tier-1 mirror\n(`modelroute.IsRemoteRoute`) against the enforcing floor over one corpus, because\n`internal/modelroute` sits below `internal/engine` and must keep its own copy of the\non-box family list. See *Placement zones* below for the third rung the binary\nlocal/remote split does not name.\n\n## Placement zones: your box, your company's boxes, someone else's\n\nLocality is not binary. A company that self-hosts runs models in **three** places, and\nan account declares which by its `kind`:\n\n| Zone | `kind` | Where the weights sit | Self-hosted? | On-box? |\n| --- | --- | --- | --- | --- |\n| `device` | `local` | the engineer's own machine (loopback only) | yes | **yes** |\n| `fleet` | `fleet` | a machine the **organization** operates | yes | no |\n| `vendor` | `openai` / `anthropic` / `gemini` / `xai` / `deepseek` / `openai-responses` | a third-party lab's API | no | no |\n\nThe middle rung is the one that carries the token volume in a self-hosting shop — a\nGLM- or Kimi-class open model on company GPUs, shared by every engineer. Before\n`kind: fleet` existed it could not be written down: as `local` it was refused (a local\naccount must carry a loopback base URL), and as `openai` it was admitted but became\nindistinguishable from `api.openai.com` in every downstream record, so the org got no\ncredit for hardware it owns.\n\n```json\n{\n \"id\": \"corp-glm\",\n \"kind\": \"fleet\",\n \"base_url\": \"http://glm.infer.corp.internal:8000/v1\",\n \"label\": \"GLM-5.2 vLLM server the company operates\"\n}\n```\n\nA `fleet` account needs an explicit, non-loopback `http(s)` endpoint (there is no public\ndefault for a host only your org can name, and a loopback address is `device` by\ndefinition — the zones must stay disjoint). Unlike a vendor account it does **not**\nrequire a `cred_env`: an org-operated server on a private network commonly has no API\nkey. Supply one when the endpoint authenticates.\n\n**Self-hosted is not the same as on-box, deliberately.** `ZoneFleet.SelfHosted()` is\ntrue — its tokens count toward \"we ran this on our own silicon\" — but\n`ZoneFleet.OnBox()` is false, so the residency floor still treats a `fleet:…` route as\nremote and still denies a tenant-scoped payload routed there. Naming the zone changes\nwhat fak can *attribute*, not what it *permits*. Letting an org-operated host carry a\nsensitive payload is an enforcement change that needs an operator-declared trust\nboundary (authenticated transport, a named host allowlist), and it is tracked\nseparately — it does not arrive as a side effect of declaring a zone.\n\nThe zones are ordered as an escalation ladder — `device` (0) → `fleet` (1) →\n`vendor` (2) — so \"prefer the cheapest rung that can do the work\" is a comparison on a\ndeclared value rather than a string switch. An unrecognized zone ranks *above* vendor\nand is neither self-hosted nor on-box: unattributable never reads as free or as safe.\n\n### Walking the ladder: `Roster.Place`\n\n`Roster.Place(class, candidates)` is what actually walks it. Given a `WorkClass` and the\nmodels you have bound — each with the work tier you have evidence it can serve — it\nreturns the cheapest rung that can take the job, plus the full ladder walk that produced\nthe answer:\n\n```go\np, err := roster.Place(modelroute.ClassRoutine, []modelroute.Candidate{\n {Model: \"tiny\", Capability: modelroute.TierT2, Measured: true},\n {Model: \"corp-mid\", Capability: modelroute.TierT1, Measured: true},\n {Model: \"frontier\", Capability: modelroute.TierT0, Measured: true},\n})\n// p.Zone == ZoneDevice, p.SelfHosted() == true, p.Escalated == false\n```\n\nTwo rules stop it from being wishful thinking:\n\n**The floor belongs to the work, not to the model.** Admission runs through\n`TierPolicy.Admit`, so a cheap rung cannot take work above its tier however available it\nis. Security/release/destructive work has a floor that never drops to routine, so it\nskips a measured local model and lands on the fleet — `p.Escalated` is true and the\ndevice rung reports `zone-under-tier`. An unrecognized class stays at the strictest floor\nrather than inferring a cheap one.\n\n**Unmeasured capability may not descend the ladder.** `Candidate.Measured` records\nwhether `Capability` is a measurement or an assertion, and an assertion is skipped on\nevery rung below the vendor. This matters right now: `internal/ablate`'s `StubTierScorer`\nmeasures nothing, so a placer that trusted asserted grades would route everything to the\ncheapest rung on a number nobody computed, and would look like it was working. Until\ncapability measurement lands, an operator opts a rung in by marking a candidate\n`Measured` — a claim they can be held to. `TestUnmeasuredCapabilityCannotDescendTheLadder`\npins both halves: an all-asserted ladder places on the vendor, and the same work moves to\nthe device the moment the cheap rung is measured.\n\nEvery rung is reported, including the ones passed over, in a closed reason vocabulary\n(`placed-in-zone`, `zone-under-tier`, `zone-capability-unmeasured`, `zone-no-candidate`,\n`zone-not-reached`, `escalated-past-cheaper-zone`) — because \"why is this *still* going to\na vendor?\" is the question an operator actually asks. A candidate the roster cannot\nresolve is a loud error, never a silent skip: a typo in a placement config must surface as\na misconfiguration rather than as traffic quietly continuing to bill a vendor.\n\nThe trap this code is written around is that `WorkTier` numbers are **inverted** — T0 is\nthe most demanding but the lowest number. Every comparison goes through\n`WorkTier.MeetsRequirement`, never a raw `<` or `>=`. Substituting the natural-looking\n`capability >= required` fails four tests at once, and the way it fails is the point: a\n4B laptop model takes both the ultra-hard and the security/destructive work.\n\n### Declaring what the work is\n\n`Place` needs a `WorkClass`; routing produces a `Subject`. `ClassOf(Subject)` is the\njoint, and it is where an automatic placer is most tempted to cheat — it would be easy to\nlook at a tool name, decide \"grep is harmless\", and route it to a laptop. fak does not do\nthat. Judging what a call can destroy is `internal/adjudicator`'s reversibility question,\nit sits a tier above this leaf, and it is deliberately not re-guessed from a name here.\n\nSo a work class is **declared, or it is conservative**. Declare it as a subject label:\n\n```jsonc\n{ \"match\": { \"aspect\": \"tool_call\", \"labels\": { \"work_class\": \"routine\" } },\n \"plan\": { \"model\": \"tiny\" } }\n```\n\n| Input | Classified as | Why |\n| --- | --- | --- |\n| `labels.work_class` naming a known class | that class, **declared** | the operator said so |\n| `aspect: \"scout\"`, nothing declared | `routine`, **declared** | a scout *is* the cheap classify-first probe — the aspect names the work |\n| anything else | *(empty)*, **undeclared** | `PolicyFor` already puts the empty class at the strictest floor |\n| `labels.work_class` misspelled | *(empty)*, **undeclared** | a typo in `routine` must not read as permission to use a laptop |\n\nUndeclared work therefore lands exactly where it landed before this feature existed — on\na vendor — and reports `class-undeclared-conservative` so an operator can see the fix is\na label rather than more hardware. There is no second conservatism mechanism layered on\ntop; one gate in one place is why the behaviour stays predictable. `Complexity` can then\nratchet a floor **up** (a high-complexity `routine` subject becomes `normal-impl`) and\nnever down, which is what makes accepting complexity as an input safe at all.\n\n`Roster.PlaceSubject(subject, candidates)` composes the two and returns the placement\n*and* the classification, so both halves of the decision are visible together.\n\n### Declaring what a sub-agent type does\n\nSub-agents are where the cheap rungs would pay for themselves — a fleet spawns far more\ndelegated work than top-level turns — and `Roster.PlaceSpawn(parent, class, candidates)`\nalready answers \"which rung should this child run on\". It refuses an **empty** work class\non purpose: an unclassified spawn is a missing classification, not a routine one, and\nletting delegation reach a laptop without stating what the work *is* would be exactly the\nshape a floor bypass takes.\n\nThat refusal is what left the call unreachable. A spawn arrives as an admitted tool call\ncarrying an agent **type** and a prompt. The prompt is prose, and reading a class out of\nprose is the guess `ClassOf` refuses everywhere else. The type is structured — but it is\nnot a work class: only an operator knows whether the agent type *their* fleet calls\n`explore` does bounded lookup or ships code.\n\nSo it is declared, in the roster the operator already owns:\n\n```jsonc\n\"spawn_classes\": [\n { \"type\": \"explore\", \"class\": \"routine\" },\n { \"type\": \"code-reviewer\", \"class\": \"normal-impl\" },\n { \"type\": \"release-cutter\", \"class\": \"security-release-destructive\" }\n]\n```\n\n`Roster.SpawnClassFor(type)` resolves one, returning `(class, declared bool)`. The rules\nare the ones the rest of this leaf runs on:\n\n| Input | Resolves to | Why |\n| --- | --- | --- |\n| a declared type (case- and space-insensitive) | that class, **declared** | the operator said so |\n| a type nobody declared, or no `spawn_classes` block at all | *(empty)*, **undeclared** | `PlaceSpawn` then refuses — the spawn keeps whatever placement it has today |\n| a declared type whose class token this package does not know | *(empty)*, **undeclared** | same rule as a misspelled `work_class` label: a typo is not permission to use a laptop |\n\nThe match is **exact**, never a prefix and never a glob — `explore` must not answer for\n`explore-and-delete`. A malformed entry is refused at load rather than skipped: an empty\ntype, an empty or unknown class (the error names the four options), a duplicate type, or a\ntype carrying a route delimiter. A silently-dropped entry is the worst outcome available,\nbecause it looks correct from the operator's side while behaving like an undeclared one,\nand nothing points at the typo.\n\n**Shipped:** the declaration, its validation, and the resolver — plus the block in\n`examples/model-accounts.example.json`, which the test suite parses and resolves.\n**Not shipped:** nothing on the spawn path calls `SpawnClassFor` yet. The gateway does\nobserve spawns (`internal/gateway/adjudicate_proposed.go` stamps every admitted tool call\nand already recognises the spawn-shaped ones — that is where `spawn_count` comes from),\nand that is the seam a placement call would bind to, but binding it changes where\ndelegated traffic runs and is a separate, individually reviewable change under\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416). Declaring\n`spawn_classes` today moves no traffic.\n\nOne neighbouring field is *not* this signal, and it has already been mistaken for it: a\nsession's `ParentTrace`/`Generation` is **continuation** lineage — the same agent after a\nbudget-reset re-continuation — written only by `session.Table.Recontinue`. It never marks\na row as somebody's child. `SpawnCount` is the sub-agent axis, and it is parent-side: it\ncounts children a trace spawned, never whose child a trace is.\n\n### Seeing the ladder before it moves traffic: `fak route --place`\n\n```console\n$ fak route --accounts accounts.json --place --labels work_class=routine\n```\n\nThe oracle walks the ladder for one subject against your real roster and prints the rung\nit chose, every rung it passed over, and the closed reason token for each. It exists\n*before* anything on the dispatch path calls `Place`, deliberately: a placement policy\nnobody can inspect is a policy nobody can be held to, and an operator has to be able to\nsee what the ladder would do with **their** roster before fak starts moving traffic on\nthe strength of it. Add `--json` for the same answer as a report.\n\nThe candidate pool is every model the roster **binds**, not just the one routing picked —\nthe question is which rung can serve this class of work at all. Compatibility-only and\ndeprecated-alias bindings are excluded, since admitting a legacy spelling would let the\nsame hardware appear twice on a rung.\n\n### Saying what a model can do\n\nBecause unmeasured capability may not descend the ladder, a fresh roster places\n*everything* on the vendor rung — correctly, and uselessly. There are three ways to give\nthe ladder something to descend on, in increasing order of what they cost to produce:\n\n| Flag | What it is | What it costs to be wrong |\n| --- | --- | --- |\n| `--capability qwen3.6-4b=t2,glm-5.2=t1` | an operator **asserting** a grade | attributable to a person, but does not scale past a handful of models |\n| `--evidence FILE` | a summary of observed outcomes, graded here | someone assembled the counts; you are trusting their arithmetic |\n| `--outcomes FILE` | the append-only turn journal, **counted here** | nothing is trusted but the record itself |\n\nAll three feed one grader (`modelroute.GradeCapability`), and its bar is yours:\n\n```console\n$ fak route --accounts accounts.json --place --labels work_class=routine \\\n --outcomes turns.jsonl --since 30d --grade-floor attempts=50,rate=0.9,witness\n```\n\nThe default bar is 20 independently verified attempts at 80%. There is deliberately **no\nknob that makes self-report count** — a model's own claim of 500/500 successes buys\nnothing, and the refused attempts are reported *as refused* rather than as absent. An\noperator who wants to assert a capability can already do so, attributably, with\n`--capability`. A model claimed by both an assertion and a measurement is a named\nrefusal: they cannot both be the grade.\n\nA grade is the **floor** of the work observed, never its optimal tier, and a model that\nfails the bar is `UNMEASURED` **with a reason** rather than graded at the worst tier. The\nthree reasons call for opposite responses and are never collapsed into one:\n\n| Reason | The fix |\n| --- | --- |\n| `no-trusted-evidence` | run the turns under a witness or a judge — the volume is there, the provenance is not |\n| `insufficient-samples` | run more turns, or lower `attempts=` if you meant to |\n| `below-success-floor` | this model cannot do this work; that is the answer |\n\n### Grading from the turn journal\n\n`--outcomes` reads the record itself — one JSON object per line, appended as turns\nhappen (`modelroute.TurnOutcome`):\n\n```jsonl\n{\"id\":\"t-4471\",\"model\":\"qwen3.6-4b\",\"class\":\"routine\",\"zone\":\"device\",\"success\":true,\"verify\":\"witness\",\"at\":\"2026-07-24T11:02:00Z\"}\n```\n\nThe fold is written to **lose** evidence rather than invent it, because each alternative\ninflates a claim, and it reports every loss:\n\n```\n journal turns.jsonl: 1463 line(s), 1332 counted, 4 model(s) with evidence\n not counted 1 unparseable line(s), 30 replayed id(s), 60 older than 30d, 40 undated\n```\n\n- A repeated `id` is counted **once**. Replaying a file is the cheapest possible way to\n manufacture a grade: the same 20 successes appended three times must not read as 60.\n- An **undated** row cannot be shown to be inside a window, so `--since` excludes it — and\n counts it apart from a stale one, because one needs a wider window and the other needs a\n producer that stamps its rows. Ask for no window and a missing date costs nothing.\n- **Provenance is never merged.** 100 self-reported turns and 20 witnessed ones about the\n same model stay two rows; merging them would force a pick of provenance, and any pick\n is a lie. The grade keeps the weakest provenance behind whatever it merged.\n- A **torn line** is skipped and counted, never fatal. A fleet appending to this file will\n eventually die mid-write, and discarding thousands of verified turns over one partial\n row would make the honest path the fragile one.\n- Rows with no `id` are kept — a missing id is not proof of a duplicate — but reported as\n a corpus that **cannot be checked for replay**, since that is a fact about how much the\n number is worth.\n\n`--evidence` and `--outcomes` are mutually exclusive: a run that took some of its answer\nfrom a journal and some from a hand-written summary produces a grade whose provenance\nnobody can state afterwards.\n\n**What is not shipped:** nothing yet *writes* this journal. `internal/ablate`'s\n`StubTierScorer` still grades nothing, so today the file is something you produce — from\nDOS's git witness, a judge score, or your own harness. That producer is the one piece\nstanding between this ladder and traffic that moves by itself.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## Managed-cache posture across launchers\n\nEvery fak launcher that fronts an agent with `fak guard` — `fak accounts launch`,\n`fak codex`, and the dispatch worker (`fak dispatch tick`/`auto`) — can hand guard a\n**managed-cache posture** that decides whether the gateway upgrades the stable-prefix\n`cache_control` breakpoint to the **1h-TTL** tier on the outbound Anthropic wire.\n\n**A subscription seat stays passive by default, on purpose.** Guard's own `--managed-cache\nauto` follows a never-speculate rule: it will not manage a wire whose billing it cannot see.\nA Pro/Max seat authenticates with subscription OAuth (`Authorization: Bearer`), whose cache\neconomics guard does not own, so `auto` resolves **passive** there and no launcher silently\nflips it. Nothing changes for a subscription fleet unless you ask for it.\n\n**An API-key-billed fleet reaches ACTIVE without hand-editing any launcher**, via two env\nknobs read once per launch and threaded onto the child's guard argv (not its environment, so\na *resumed* child — whose gateway env is deliberately stripped — keeps the posture):\n\n| Knob | Effect |\n|------|--------|\n| `FAK_GUARD_API_KEY_ENV=ANTHROPIC_API_KEY` | Names the env var holding the API key. On the Anthropic wire this is the explicit opt-IN to API billing that lets `auto` resolve **ACTIVE** (guard bills the key, not the seat's subscription). |\n| `FAK_MANAGED_CACHE=on\\|off\\|auto` | Forces the posture. `on` upgrades regardless of billing; `off` disables; `auto` (default) defers to the billing signal above. |\n\n`fak accounts launch --managed-cache ` and `fak codex --managed-cache ` expose\nthe same lever per-launch (defaulting to `$FAK_MANAGED_CACHE`); the dispatch worker reads the\nenv knobs only. An unknown mode fails loud at the front-ends; the headless worker warns and\nfalls back to `auto` so a fleet turn never dies over a cache-posture typo. Either way the\nguard child prints the RESOLVED posture in its startup banner, so the worker log witnesses\nwhether the 1h upgrade actually engaged. An unconfigured fleet emits no `--managed-cache`\nflag at all — the guard argv is byte-identical to before this knob existed.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch\n([#2528](https://github.com/anthony-chaudhary/fak/issues/2528)). v1 *resolves and prints*\na binding; it does not yet send a request over the wire to your chosen account. Building\nthe planners from the Targets and running an ensemble's members is the additive dispatch\nwiring, the same shipped-vs-deferred split the routing spine uses for the ensemble fold\nversus its execution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n\n**Placement zones — shipped and deferred, precisely.** *Shipped:* the vocabulary. A\ncompany-operated server is expressible (`kind: fleet`), its account validates on its own\nterms, its route string round-trips to `ZoneFleet`, and `PlacementZone.SelfHosted()`\ngives every downstream consumer one predicate to attribute a token by. Witnessed by\n`TestZonesPartitionTheKnownKinds`, `TestEngineRouteCarriesTheZoneBackOut`,\n`TestShippedExampleRosterCarriesAllThreeZones`, and — at the enforcing floor —\n`TestFleetZoneStaysRemoteAtTheFloor` plus `TestTierOneRouteMirrorAgreesWithTheFloor`.\n*Also shipped:* `Roster.Place` **chooses** a zone (see *Walking the ladder* above).\n*Deferred, and deliberately so:* nothing yet calls it on the live dispatch path, the\nserving path does not yet **record** the zone it used, and the residency floor does not\nyet treat a fleet host as inside a trust boundary. Zone-aware placement, the self-hosted\ntoken-fraction metric, and the escalation ladder are separate, individually reviewable\nchanges on top of this vocabulary — not implied by it. They are tracked as\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416).\n\n### The first consumer: honest stratum attribution\n\n`internal/sessionaudit` classifies audited turns into billing buckets. It used to answer\n\"was this self-hosted?\" from the **model id**, which cannot support the claim: Qwen,\nLlama, Mistral, DeepSeek, GLM and Kimi all publish open weights, so each is servable\neither from hardware you operate or from a vendor's API. That produced two contradictions\nin one file — `deepseek*` was bucketed self-hosted while priced from the published\nDeepSeek *API* rate card, and the roster above binds `qwen/qwen3.6-27b` to **Groq**, a\nvendor, while the classifier called those tokens self-hosted.\n\nA model id now buys only a model-family answer (`open-weights (placement unknown)`), and\nplacement comes from a real signal:\n\n| Call | Answers |\n| --- | --- |\n| `ProviderBucket(model)` | whose *model* it is — never where it ran |\n| `BucketForPlacement(model, zone)` | the bucket, given a `PlacementZone` string |\n| `BucketIsSelfHosted(bucket)` | `(selfHosted, known bool)` — two values on purpose |\n\n`BucketIsSelfHosted` returns a second `known` bool so unattributable volume cannot be\nfolded into either side of a self-hosted fraction. `TestPricedModelsAreNeverBucketedSelfHosted`\nmakes the underlying rule executable and total over the rate card: a model billed per token\nby a third party ran on their hardware, so it can never also be bucketed as self-hosted.\n"}, "896f714b83ea1b4ff22a9615b7a36752e230e841": {"t": 1785749571.908621, "n": 31322, "h": "c41f44208c77e96f", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, DeepSeek, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, Groq, DeepSeek, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product sibling of the fleet account switcher (`fak fleet-accounts`,\nthe native Go successor to the legacy `tools/fleet_accounts.py` shim): provider-neutral,\ncredential-safe, and composable with the routing spine. It lives in\n`internal/modelroute/account.go` (pure, stdlib-only, the same package as the routing\ndecision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\nOpenAI-compatible providers such as Groq are represented as `kind: \"openai\"` plus their\nprovider `base_url`, so the wire adapter stays generic while the account id names whose\ncredential is being used.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" },\n { \"id\": \"july6netra_groq\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 1000,\n \"tokens_per_minute\": 8000, \"tokens_per_day\": 200000 },\n { \"id\": \"july6netra_groq_compound\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 250 },\n { \"id\": \"deepseek\", \"kind\": \"deepseek\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 },\n { \"id\": \"deepseek-anthropic\", \"kind\": \"anthropic\",\n \"base_url\": \"https://api.deepseek.com/anthropic\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" },\n { \"model\": \"qwen36-groq\", \"account\": \"july6netra_groq\", \"upstream_model\": \"qwen/qwen3.6-27b\" },\n { \"model\": \"groq-compound\", \"account\": \"july6netra_groq_compound\", \"upstream_model\": \"groq/compound\" },\n { \"model\": \"deepseek-pro\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-pro\" },\n { \"model\": \"deepseek-flash\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-flash\" },\n { \"model\": \"deepseek-chat-compat\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-chat\",\n \"compatibility_only\": true, \"deprecated_after_utc\": \"2026-07-24 15:59 UTC\",\n \"deprecated_alias_for\": \"deepseek-v4-flash non-thinking mode\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nCoding-agent subscriptions add a second layer: **config-home accounts**. Claude uses\n`CLAUDE_CONFIG_DIR` homes such as `~/.claude-gem8-seat`; Codex uses `CODEX_HOME` homes such as\n`~/.codex` or `~/.codex-work`. `fak fleet-accounts` discovers both, derives their non-secret\nprovider-account identity, collapses duplicate homes on the same rate-limit bucket, and offers\nonly homes whose credential state is ready. The default Codex picker profile is\n`gpt-5.6-sol` with `model_reasoning_effort=xhigh`.\n\nThe authored lifecycle registry behind `fak accounts` remains Claude-specific today; its seat\nnames and tombstones are therefore applied only to Claude rows. Codex rows take lifecycle/login\ntruth from their own home (`auth.json`/`config.toml`) and never inherit a same-named Claude seat\nsuch as `default`.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## API-host probe bridge\n\nThe same model-account roster can feed the API-host no-spend probes:\n\n```bash\nfak api-host readiness --from-model-accounts examples/model-accounts.example.json\nfak api-host acceptance --from-model-accounts examples/model-accounts.example.json\n```\n\n`readiness` converts only OpenAI-compatible or local accounts into `/models` probe targets\n(`openai`, `openai-responses`, `xai`, `deepseek`, and `local`). Native provider accounts are not guessed into\nan OpenAI-shaped probe. `acceptance` keeps every account with a probeable base URL visible:\nOpenAI-compatible/local accounts can become `READY_FOR_LIVE_BRIDGE_RUN`, while native\nAnthropic/Gemini accounts are reported as `WIRE_SUPPORTED_UNPROBED`. Model hints come from the\nroster bindings, so the API-host report stays tied to the same abstract model ids the route policy\nuses.\n\nGroq Qwen3.6 uses the OpenAI-compatible base `https://api.groq.com/openai/v1`, credential\nenv var `FAK_GROQ_API_KEY`, and upstream model slug `qwen/qwen3.6-27b`. The example records\nthe current account/model limits as metadata: 30 requests/minute, 1,000 requests/day,\n8,000 tokens/minute, and 200,000 tokens/day. Groq Compound uses the same base and env var\nunder the separate account id `july6netra_groq_compound`, bound as the lower-quality\n`groq-compound` target to upstream slug `groq/compound`; its metadata is request-only:\n30 requests/minute and 250 requests/day, with no token-minute or token-day cap recorded.\nThe Claude dogfood launcher separately caps the outbound `max_tokens` field at 8192 for\n`groq/compound`, because Groq rejects larger per-response output budgets. Those values\nare advisory metadata for routing and launchers; the v1 resolver does not throttle\nrequests.\n\nDeepSeek V4 uses `deepseek-v4-pro` / `deepseek-v4-flash` on the OpenAI-compatible base\n`https://api.deepseek.com`; its Anthropic-compatible profile uses\n`https://api.deepseek.com/anthropic`. The legacy `deepseek-chat` and `deepseek-reasoner`\naliases are accepted only when marked `compatibility_only`, with the documented retirement\nmetadata `2026-07-24 15:59 UTC`, so a new roster does not silently bind to an alias that is\nabout to disappear.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open. The same file pins the tier-1 mirror\n(`modelroute.IsRemoteRoute`) against the enforcing floor over one corpus, because\n`internal/modelroute` sits below `internal/engine` and must keep its own copy of the\non-box family list. See *Placement zones* below for the third rung the binary\nlocal/remote split does not name.\n\n## Placement zones: your box, your company's boxes, someone else's\n\nLocality is not binary. A company that self-hosts runs models in **three** places, and\nan account declares which by its `kind`:\n\n| Zone | `kind` | Where the weights sit | Self-hosted? | On-box? |\n| --- | --- | --- | --- | --- |\n| `device` | `local` | the engineer's own machine (loopback only) | yes | **yes** |\n| `fleet` | `fleet` | a machine the **organization** operates | yes | no |\n| `vendor` | `openai` / `anthropic` / `gemini` / `xai` / `deepseek` / `openai-responses` | a third-party lab's API | no | no |\n\nThe middle rung is the one that carries the token volume in a self-hosting shop — a\nGLM- or Kimi-class open model on company GPUs, shared by every engineer. Before\n`kind: fleet` existed it could not be written down: as `local` it was refused (a local\naccount must carry a loopback base URL), and as `openai` it was admitted but became\nindistinguishable from `api.openai.com` in every downstream record, so the org got no\ncredit for hardware it owns.\n\n```json\n{\n \"id\": \"corp-glm\",\n \"kind\": \"fleet\",\n \"base_url\": \"http://glm.infer.corp.internal:8000/v1\",\n \"label\": \"GLM-5.2 vLLM server the company operates\"\n}\n```\n\nA `fleet` account needs an explicit, non-loopback `http(s)` endpoint (there is no public\ndefault for a host only your org can name, and a loopback address is `device` by\ndefinition — the zones must stay disjoint). Unlike a vendor account it does **not**\nrequire a `cred_env`: an org-operated server on a private network commonly has no API\nkey. Supply one when the endpoint authenticates.\n\n**Self-hosted is not the same as on-box, deliberately.** `ZoneFleet.SelfHosted()` is\ntrue — its tokens count toward \"we ran this on our own silicon\" — but\n`ZoneFleet.OnBox()` is false, so the residency floor still treats a `fleet:…` route as\nremote and still denies a tenant-scoped payload routed there. Naming the zone changes\nwhat fak can *attribute*, not what it *permits*. Letting an org-operated host carry a\nsensitive payload is an enforcement change that needs an operator-declared trust\nboundary (authenticated transport, a named host allowlist), and it is tracked\nseparately — it does not arrive as a side effect of declaring a zone.\n\nThe zones are ordered as an escalation ladder — `device` (0) → `fleet` (1) →\n`vendor` (2) — so \"prefer the cheapest rung that can do the work\" is a comparison on a\ndeclared value rather than a string switch. An unrecognized zone ranks *above* vendor\nand is neither self-hosted nor on-box: unattributable never reads as free or as safe.\n\n### Walking the ladder: `Roster.Place`\n\n`Roster.Place(class, candidates)` is what actually walks it. Given a `WorkClass` and the\nmodels you have bound — each with the work tier you have evidence it can serve — it\nreturns the cheapest rung that can take the job, plus the full ladder walk that produced\nthe answer:\n\n```go\np, err := roster.Place(modelroute.ClassRoutine, []modelroute.Candidate{\n {Model: \"tiny\", Capability: modelroute.TierT2, Measured: true},\n {Model: \"corp-mid\", Capability: modelroute.TierT1, Measured: true},\n {Model: \"frontier\", Capability: modelroute.TierT0, Measured: true},\n})\n// p.Zone == ZoneDevice, p.SelfHosted() == true, p.Escalated == false\n```\n\nTwo rules stop it from being wishful thinking:\n\n**The floor belongs to the work, not to the model.** Admission runs through\n`TierPolicy.Admit`, so a cheap rung cannot take work above its tier however available it\nis. Security/release/destructive work has a floor that never drops to routine, so it\nskips a measured local model and lands on the fleet — `p.Escalated` is true and the\ndevice rung reports `zone-under-tier`. An unrecognized class stays at the strictest floor\nrather than inferring a cheap one.\n\n**Unmeasured capability may not descend the ladder.** `Candidate.Measured` records\nwhether `Capability` is a measurement or an assertion, and an assertion is skipped on\nevery rung below the vendor. This matters right now: `internal/ablate`'s `StubTierScorer`\nmeasures nothing, so a placer that trusted asserted grades would route everything to the\ncheapest rung on a number nobody computed, and would look like it was working. Until\ncapability measurement lands, an operator opts a rung in by marking a candidate\n`Measured` — a claim they can be held to. `TestUnmeasuredCapabilityCannotDescendTheLadder`\npins both halves: an all-asserted ladder places on the vendor, and the same work moves to\nthe device the moment the cheap rung is measured.\n\nEvery rung is reported, including the ones passed over, in a closed reason vocabulary\n(`placed-in-zone`, `zone-under-tier`, `zone-capability-unmeasured`, `zone-no-candidate`,\n`zone-not-reached`, `escalated-past-cheaper-zone`) — because \"why is this *still* going to\na vendor?\" is the question an operator actually asks. A candidate the roster cannot\nresolve is a loud error, never a silent skip: a typo in a placement config must surface as\na misconfiguration rather than as traffic quietly continuing to bill a vendor.\n\nThe trap this code is written around is that `WorkTier` numbers are **inverted** — T0 is\nthe most demanding but the lowest number. Every comparison goes through\n`WorkTier.MeetsRequirement`, never a raw `<` or `>=`. Substituting the natural-looking\n`capability >= required` fails four tests at once, and the way it fails is the point: a\n4B laptop model takes both the ultra-hard and the security/destructive work.\n\n### Declaring what the work is\n\n`Place` needs a `WorkClass`; routing produces a `Subject`. `ClassOf(Subject)` is the\njoint, and it is where an automatic placer is most tempted to cheat — it would be easy to\nlook at a tool name, decide \"grep is harmless\", and route it to a laptop. fak does not do\nthat. Judging what a call can destroy is `internal/adjudicator`'s reversibility question,\nit sits a tier above this leaf, and it is deliberately not re-guessed from a name here.\n\nSo a work class is **declared, or it is conservative**. Declare it as a subject label:\n\n```jsonc\n{ \"match\": { \"aspect\": \"tool_call\", \"labels\": { \"work_class\": \"routine\" } },\n \"plan\": { \"model\": \"tiny\" } }\n```\n\n| Input | Classified as | Why |\n| --- | --- | --- |\n| `labels.work_class` naming a known class | that class, **declared** | the operator said so |\n| `aspect: \"scout\"`, nothing declared | `routine`, **declared** | a scout *is* the cheap classify-first probe — the aspect names the work |\n| anything else | *(empty)*, **undeclared** | `PolicyFor` already puts the empty class at the strictest floor |\n| `labels.work_class` misspelled | *(empty)*, **undeclared** | a typo in `routine` must not read as permission to use a laptop |\n\nUndeclared work therefore lands exactly where it landed before this feature existed — on\na vendor — and reports `class-undeclared-conservative` so an operator can see the fix is\na label rather than more hardware. There is no second conservatism mechanism layered on\ntop; one gate in one place is why the behaviour stays predictable. `Complexity` can then\nratchet a floor **up** (a high-complexity `routine` subject becomes `normal-impl`) and\nnever down, which is what makes accepting complexity as an input safe at all.\n\n`Roster.PlaceSubject(subject, candidates)` composes the two and returns the placement\n*and* the classification, so both halves of the decision are visible together.\n\n### Seeing the ladder before it moves traffic: `fak route --place`\n\n```console\n$ fak route --accounts accounts.json --place --labels work_class=routine\n```\n\nThe oracle walks the ladder for one subject against your real roster and prints the rung\nit chose, every rung it passed over, and the closed reason token for each. It exists\n*before* anything on the dispatch path calls `Place`, deliberately: a placement policy\nnobody can inspect is a policy nobody can be held to, and an operator has to be able to\nsee what the ladder would do with **their** roster before fak starts moving traffic on\nthe strength of it. Add `--json` for the same answer as a report.\n\nThe candidate pool is every model the roster **binds**, not just the one routing picked —\nthe question is which rung can serve this class of work at all. Compatibility-only and\ndeprecated-alias bindings are excluded, since admitting a legacy spelling would let the\nsame hardware appear twice on a rung.\n\n### Saying what a model can do\n\nBecause unmeasured capability may not descend the ladder, a fresh roster places\n*everything* on the vendor rung — correctly, and uselessly. There are three ways to give\nthe ladder something to descend on, in increasing order of what they cost to produce:\n\n| Flag | What it is | What it costs to be wrong |\n| --- | --- | --- |\n| `--capability qwen3.6-4b=t2,glm-5.2=t1` | an operator **asserting** a grade | attributable to a person, but does not scale past a handful of models |\n| `--evidence FILE` | a summary of observed outcomes, graded here | someone assembled the counts; you are trusting their arithmetic |\n| `--outcomes FILE` | the append-only turn journal, **counted here** | nothing is trusted but the record itself |\n\nAll three feed one grader (`modelroute.GradeCapability`), and its bar is yours:\n\n```console\n$ fak route --accounts accounts.json --place --labels work_class=routine \\\n --outcomes turns.jsonl --since 30d --grade-floor attempts=50,rate=0.9,witness\n```\n\nThe default bar is 20 independently verified attempts at 80%. There is deliberately **no\nknob that makes self-report count** — a model's own claim of 500/500 successes buys\nnothing, and the refused attempts are reported *as refused* rather than as absent. An\noperator who wants to assert a capability can already do so, attributably, with\n`--capability`. A model claimed by both an assertion and a measurement is a named\nrefusal: they cannot both be the grade.\n\nA grade is the **floor** of the work observed, never its optimal tier, and a model that\nfails the bar is `UNMEASURED` **with a reason** rather than graded at the worst tier. The\nthree reasons call for opposite responses and are never collapsed into one:\n\n| Reason | The fix |\n| --- | --- |\n| `no-trusted-evidence` | run the turns under a witness or a judge — the volume is there, the provenance is not |\n| `insufficient-samples` | run more turns, or lower `attempts=` if you meant to |\n| `below-success-floor` | this model cannot do this work; that is the answer |\n\n### Grading from the turn journal\n\n`--outcomes` reads the record itself — one JSON object per line, appended as turns\nhappen (`modelroute.TurnOutcome`):\n\n```jsonl\n{\"id\":\"t-4471\",\"model\":\"qwen3.6-4b\",\"class\":\"routine\",\"zone\":\"device\",\"success\":true,\"verify\":\"witness\",\"at\":\"2026-07-24T11:02:00Z\"}\n```\n\nThe fold is written to **lose** evidence rather than invent it, because each alternative\ninflates a claim, and it reports every loss:\n\n```\n journal turns.jsonl: 1463 line(s), 1332 counted, 4 model(s) with evidence\n not counted 1 unparseable line(s), 30 replayed id(s), 60 older than 30d, 40 undated\n```\n\n- A repeated `id` is counted **once**. Replaying a file is the cheapest possible way to\n manufacture a grade: the same 20 successes appended three times must not read as 60.\n- An **undated** row cannot be shown to be inside a window, so `--since` excludes it — and\n counts it apart from a stale one, because one needs a wider window and the other needs a\n producer that stamps its rows. Ask for no window and a missing date costs nothing.\n- **Provenance is never merged.** 100 self-reported turns and 20 witnessed ones about the\n same model stay two rows; merging them would force a pick of provenance, and any pick\n is a lie. The grade keeps the weakest provenance behind whatever it merged.\n- A **torn line** is skipped and counted, never fatal. A fleet appending to this file will\n eventually die mid-write, and discarding thousands of verified turns over one partial\n row would make the honest path the fragile one.\n- Rows with no `id` are kept — a missing id is not proof of a duplicate — but reported as\n a corpus that **cannot be checked for replay**, since that is a fact about how much the\n number is worth.\n\n`--evidence` and `--outcomes` are mutually exclusive: a run that took some of its answer\nfrom a journal and some from a hand-written summary produces a grade whose provenance\nnobody can state afterwards.\n\n**What is not shipped:** nothing yet *writes* this journal. `internal/ablate`'s\n`StubTierScorer` still grades nothing, so today the file is something you produce — from\nDOS's git witness, a judge score, or your own harness. That producer is the one piece\nstanding between this ladder and traffic that moves by itself.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## Managed-cache posture across launchers\n\nEvery fak launcher that fronts an agent with `fak guard` — `fak accounts launch`,\n`fak codex`, and the dispatch worker (`fak dispatch tick`/`auto`) — can hand guard a\n**managed-cache posture** that decides whether the gateway upgrades the stable-prefix\n`cache_control` breakpoint to the **1h-TTL** tier on the outbound Anthropic wire.\n\n**A subscription seat stays passive by default, on purpose.** Guard's own `--managed-cache\nauto` follows a never-speculate rule: it will not manage a wire whose billing it cannot see.\nA Pro/Max seat authenticates with subscription OAuth (`Authorization: Bearer`), whose cache\neconomics guard does not own, so `auto` resolves **passive** there and no launcher silently\nflips it. Nothing changes for a subscription fleet unless you ask for it.\n\n**An API-key-billed fleet reaches ACTIVE without hand-editing any launcher**, via two env\nknobs read once per launch and threaded onto the child's guard argv (not its environment, so\na *resumed* child — whose gateway env is deliberately stripped — keeps the posture):\n\n| Knob | Effect |\n|------|--------|\n| `FAK_GUARD_API_KEY_ENV=ANTHROPIC_API_KEY` | Names the env var holding the API key. On the Anthropic wire this is the explicit opt-IN to API billing that lets `auto` resolve **ACTIVE** (guard bills the key, not the seat's subscription). |\n| `FAK_MANAGED_CACHE=on\\|off\\|auto` | Forces the posture. `on` upgrades regardless of billing; `off` disables; `auto` (default) defers to the billing signal above. |\n\n`fak accounts launch --managed-cache ` and `fak codex --managed-cache ` expose\nthe same lever per-launch (defaulting to `$FAK_MANAGED_CACHE`); the dispatch worker reads the\nenv knobs only. An unknown mode fails loud at the front-ends; the headless worker warns and\nfalls back to `auto` so a fleet turn never dies over a cache-posture typo. Either way the\nguard child prints the RESOLVED posture in its startup banner, so the worker log witnesses\nwhether the 1h upgrade actually engaged. An unconfigured fleet emits no `--managed-cache`\nflag at all — the guard argv is byte-identical to before this knob existed.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch\n([#2528](https://github.com/anthony-chaudhary/fak/issues/2528)). v1 *resolves and prints*\na binding; it does not yet send a request over the wire to your chosen account. Building\nthe planners from the Targets and running an ensemble's members is the additive dispatch\nwiring, the same shipped-vs-deferred split the routing spine uses for the ensemble fold\nversus its execution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n\n**Placement zones — shipped and deferred, precisely.** *Shipped:* the vocabulary. A\ncompany-operated server is expressible (`kind: fleet`), its account validates on its own\nterms, its route string round-trips to `ZoneFleet`, and `PlacementZone.SelfHosted()`\ngives every downstream consumer one predicate to attribute a token by. Witnessed by\n`TestZonesPartitionTheKnownKinds`, `TestEngineRouteCarriesTheZoneBackOut`,\n`TestShippedExampleRosterCarriesAllThreeZones`, and — at the enforcing floor —\n`TestFleetZoneStaysRemoteAtTheFloor` plus `TestTierOneRouteMirrorAgreesWithTheFloor`.\n*Also shipped:* `Roster.Place` **chooses** a zone (see *Walking the ladder* above).\n*Deferred, and deliberately so:* nothing yet calls it on the live dispatch path, the\nserving path does not yet **record** the zone it used, and the residency floor does not\nyet treat a fleet host as inside a trust boundary. Zone-aware placement, the self-hosted\ntoken-fraction metric, and the escalation ladder are separate, individually reviewable\nchanges on top of this vocabulary — not implied by it. They are tracked as\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416).\n\n### The first consumer: honest stratum attribution\n\n`internal/sessionaudit` classifies audited turns into billing buckets. It used to answer\n\"was this self-hosted?\" from the **model id**, which cannot support the claim: Qwen,\nLlama, Mistral, DeepSeek, GLM and Kimi all publish open weights, so each is servable\neither from hardware you operate or from a vendor's API. That produced two contradictions\nin one file — `deepseek*` was bucketed self-hosted while priced from the published\nDeepSeek *API* rate card, and the roster above binds `qwen/qwen3.6-27b` to **Groq**, a\nvendor, while the classifier called those tokens self-hosted.\n\nA model id now buys only a model-family answer (`open-weights (placement unknown)`), and\nplacement comes from a real signal:\n\n| Call | Answers |\n| --- | --- |\n| `ProviderBucket(model)` | whose *model* it is — never where it ran |\n| `BucketForPlacement(model, zone)` | the bucket, given a `PlacementZone` string |\n| `BucketIsSelfHosted(bucket)` | `(selfHosted, known bool)` — two values on purpose |\n\n`BucketIsSelfHosted` returns a second `known` bool so unattributable volume cannot be\nfolded into either side of a self-hosted fraction. `TestPricedModelsAreNeverBucketedSelfHosted`\nmakes the underlying rule executable and total over the rate card: a model billed per token\nby a third party ran on their hardware, so it can never also be bucketed as self-hosted.\n"}, "2183d4edcd59229ee58c7bf4db4798ae4d26d2df": {"t": 1785749573.4058843, "n": 26053, "h": "aa7bb41f263cacd8", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, DeepSeek, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, Groq, DeepSeek, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product sibling of the fleet account switcher (`fak fleet-accounts`,\nthe native Go successor to the legacy `tools/fleet_accounts.py` shim): provider-neutral,\ncredential-safe, and composable with the routing spine. It lives in\n`internal/modelroute/account.go` (pure, stdlib-only, the same package as the routing\ndecision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\nOpenAI-compatible providers such as Groq are represented as `kind: \"openai\"` plus their\nprovider `base_url`, so the wire adapter stays generic while the account id names whose\ncredential is being used.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" },\n { \"id\": \"july6netra_groq\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 1000,\n \"tokens_per_minute\": 8000, \"tokens_per_day\": 200000 },\n { \"id\": \"july6netra_groq_compound\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 250 },\n { \"id\": \"deepseek\", \"kind\": \"deepseek\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 },\n { \"id\": \"deepseek-anthropic\", \"kind\": \"anthropic\",\n \"base_url\": \"https://api.deepseek.com/anthropic\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" },\n { \"model\": \"qwen36-groq\", \"account\": \"july6netra_groq\", \"upstream_model\": \"qwen/qwen3.6-27b\" },\n { \"model\": \"groq-compound\", \"account\": \"july6netra_groq_compound\", \"upstream_model\": \"groq/compound\" },\n { \"model\": \"deepseek-pro\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-pro\" },\n { \"model\": \"deepseek-flash\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-flash\" },\n { \"model\": \"deepseek-chat-compat\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-chat\",\n \"compatibility_only\": true, \"deprecated_after_utc\": \"2026-07-24 15:59 UTC\",\n \"deprecated_alias_for\": \"deepseek-v4-flash non-thinking mode\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nCoding-agent subscriptions add a second layer: **config-home accounts**. Claude uses\n`CLAUDE_CONFIG_DIR` homes such as `~/.claude-gem8-seat`; Codex uses `CODEX_HOME` homes such as\n`~/.codex` or `~/.codex-work`. `fak fleet-accounts` discovers both, derives their non-secret\nprovider-account identity, collapses duplicate homes on the same rate-limit bucket, and offers\nonly homes whose credential state is ready. The default Codex picker profile is\n`gpt-5.6-sol` with `model_reasoning_effort=xhigh`.\n\nThe authored lifecycle registry behind `fak accounts` remains Claude-specific today; its seat\nnames and tombstones are therefore applied only to Claude rows. Codex rows take lifecycle/login\ntruth from their own home (`auth.json`/`config.toml`) and never inherit a same-named Claude seat\nsuch as `default`.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## API-host probe bridge\n\nThe same model-account roster can feed the API-host no-spend probes:\n\n```bash\nfak api-host readiness --from-model-accounts examples/model-accounts.example.json\nfak api-host acceptance --from-model-accounts examples/model-accounts.example.json\n```\n\n`readiness` converts only OpenAI-compatible or local accounts into `/models` probe targets\n(`openai`, `openai-responses`, `xai`, `deepseek`, and `local`). Native provider accounts are not guessed into\nan OpenAI-shaped probe. `acceptance` keeps every account with a probeable base URL visible:\nOpenAI-compatible/local accounts can become `READY_FOR_LIVE_BRIDGE_RUN`, while native\nAnthropic/Gemini accounts are reported as `WIRE_SUPPORTED_UNPROBED`. Model hints come from the\nroster bindings, so the API-host report stays tied to the same abstract model ids the route policy\nuses.\n\nGroq Qwen3.6 uses the OpenAI-compatible base `https://api.groq.com/openai/v1`, credential\nenv var `FAK_GROQ_API_KEY`, and upstream model slug `qwen/qwen3.6-27b`. The example records\nthe current account/model limits as metadata: 30 requests/minute, 1,000 requests/day,\n8,000 tokens/minute, and 200,000 tokens/day. Groq Compound uses the same base and env var\nunder the separate account id `july6netra_groq_compound`, bound as the lower-quality\n`groq-compound` target to upstream slug `groq/compound`; its metadata is request-only:\n30 requests/minute and 250 requests/day, with no token-minute or token-day cap recorded.\nThe Claude dogfood launcher separately caps the outbound `max_tokens` field at 8192 for\n`groq/compound`, because Groq rejects larger per-response output budgets. Those values\nare advisory metadata for routing and launchers; the v1 resolver does not throttle\nrequests.\n\nDeepSeek V4 uses `deepseek-v4-pro` / `deepseek-v4-flash` on the OpenAI-compatible base\n`https://api.deepseek.com`; its Anthropic-compatible profile uses\n`https://api.deepseek.com/anthropic`. The legacy `deepseek-chat` and `deepseek-reasoner`\naliases are accepted only when marked `compatibility_only`, with the documented retirement\nmetadata `2026-07-24 15:59 UTC`, so a new roster does not silently bind to an alias that is\nabout to disappear.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open. The same file pins the tier-1 mirror\n(`modelroute.IsRemoteRoute`) against the enforcing floor over one corpus, because\n`internal/modelroute` sits below `internal/engine` and must keep its own copy of the\non-box family list. See *Placement zones* below for the third rung the binary\nlocal/remote split does not name.\n\n## Placement zones: your box, your company's boxes, someone else's\n\nLocality is not binary. A company that self-hosts runs models in **three** places, and\nan account declares which by its `kind`:\n\n| Zone | `kind` | Where the weights sit | Self-hosted? | On-box? |\n| --- | --- | --- | --- | --- |\n| `device` | `local` | the engineer's own machine (loopback only) | yes | **yes** |\n| `fleet` | `fleet` | a machine the **organization** operates | yes | no |\n| `vendor` | `openai` / `anthropic` / `gemini` / `xai` / `deepseek` / `openai-responses` | a third-party lab's API | no | no |\n\nThe middle rung is the one that carries the token volume in a self-hosting shop — a\nGLM- or Kimi-class open model on company GPUs, shared by every engineer. Before\n`kind: fleet` existed it could not be written down: as `local` it was refused (a local\naccount must carry a loopback base URL), and as `openai` it was admitted but became\nindistinguishable from `api.openai.com` in every downstream record, so the org got no\ncredit for hardware it owns.\n\n```json\n{\n \"id\": \"corp-glm\",\n \"kind\": \"fleet\",\n \"base_url\": \"http://glm.infer.corp.internal:8000/v1\",\n \"label\": \"GLM-5.2 vLLM server the company operates\"\n}\n```\n\nA `fleet` account needs an explicit, non-loopback `http(s)` endpoint (there is no public\ndefault for a host only your org can name, and a loopback address is `device` by\ndefinition — the zones must stay disjoint). Unlike a vendor account it does **not**\nrequire a `cred_env`: an org-operated server on a private network commonly has no API\nkey. Supply one when the endpoint authenticates.\n\n**Self-hosted is not the same as on-box, deliberately.** `ZoneFleet.SelfHosted()` is\ntrue — its tokens count toward \"we ran this on our own silicon\" — but\n`ZoneFleet.OnBox()` is false, so the residency floor still treats a `fleet:…` route as\nremote and still denies a tenant-scoped payload routed there. Naming the zone changes\nwhat fak can *attribute*, not what it *permits*. Letting an org-operated host carry a\nsensitive payload is an enforcement change that needs an operator-declared trust\nboundary (authenticated transport, a named host allowlist), and it is tracked\nseparately — it does not arrive as a side effect of declaring a zone.\n\nThe zones are ordered as an escalation ladder — `device` (0) → `fleet` (1) →\n`vendor` (2) — so \"prefer the cheapest rung that can do the work\" is a comparison on a\ndeclared value rather than a string switch. An unrecognized zone ranks *above* vendor\nand is neither self-hosted nor on-box: unattributable never reads as free or as safe.\n\n### Walking the ladder: `Roster.Place`\n\n`Roster.Place(class, candidates)` is what actually walks it. Given a `WorkClass` and the\nmodels you have bound — each with the work tier you have evidence it can serve — it\nreturns the cheapest rung that can take the job, plus the full ladder walk that produced\nthe answer:\n\n```go\np, err := roster.Place(modelroute.ClassRoutine, []modelroute.Candidate{\n {Model: \"tiny\", Capability: modelroute.TierT2, Measured: true},\n {Model: \"corp-mid\", Capability: modelroute.TierT1, Measured: true},\n {Model: \"frontier\", Capability: modelroute.TierT0, Measured: true},\n})\n// p.Zone == ZoneDevice, p.SelfHosted() == true, p.Escalated == false\n```\n\nTwo rules stop it from being wishful thinking:\n\n**The floor belongs to the work, not to the model.** Admission runs through\n`TierPolicy.Admit`, so a cheap rung cannot take work above its tier however available it\nis. Security/release/destructive work has a floor that never drops to routine, so it\nskips a measured local model and lands on the fleet — `p.Escalated` is true and the\ndevice rung reports `zone-under-tier`. An unrecognized class stays at the strictest floor\nrather than inferring a cheap one.\n\n**Unmeasured capability may not descend the ladder.** `Candidate.Measured` records\nwhether `Capability` is a measurement or an assertion, and an assertion is skipped on\nevery rung below the vendor. This matters right now: `internal/ablate`'s `StubTierScorer`\nmeasures nothing, so a placer that trusted asserted grades would route everything to the\ncheapest rung on a number nobody computed, and would look like it was working. Until\ncapability measurement lands, an operator opts a rung in by marking a candidate\n`Measured` — a claim they can be held to. `TestUnmeasuredCapabilityCannotDescendTheLadder`\npins both halves: an all-asserted ladder places on the vendor, and the same work moves to\nthe device the moment the cheap rung is measured.\n\nEvery rung is reported, including the ones passed over, in a closed reason vocabulary\n(`placed-in-zone`, `zone-under-tier`, `zone-capability-unmeasured`, `zone-no-candidate`,\n`zone-not-reached`, `escalated-past-cheaper-zone`) — because \"why is this *still* going to\na vendor?\" is the question an operator actually asks. A candidate the roster cannot\nresolve is a loud error, never a silent skip: a typo in a placement config must surface as\na misconfiguration rather than as traffic quietly continuing to bill a vendor.\n\nThe trap this code is written around is that `WorkTier` numbers are **inverted** — T0 is\nthe most demanding but the lowest number. Every comparison goes through\n`WorkTier.MeetsRequirement`, never a raw `<` or `>=`. Substituting the natural-looking\n`capability >= required` fails four tests at once, and the way it fails is the point: a\n4B laptop model takes both the ultra-hard and the security/destructive work.\n\n### Declaring what the work is\n\n`Place` needs a `WorkClass`; routing produces a `Subject`. `ClassOf(Subject)` is the\njoint, and it is where an automatic placer is most tempted to cheat — it would be easy to\nlook at a tool name, decide \"grep is harmless\", and route it to a laptop. fak does not do\nthat. Judging what a call can destroy is `internal/adjudicator`'s reversibility question,\nit sits a tier above this leaf, and it is deliberately not re-guessed from a name here.\n\nSo a work class is **declared, or it is conservative**. Declare it as a subject label:\n\n```jsonc\n{ \"match\": { \"aspect\": \"tool_call\", \"labels\": { \"work_class\": \"routine\" } },\n \"plan\": { \"model\": \"tiny\" } }\n```\n\n| Input | Classified as | Why |\n| --- | --- | --- |\n| `labels.work_class` naming a known class | that class, **declared** | the operator said so |\n| `aspect: \"scout\"`, nothing declared | `routine`, **declared** | a scout *is* the cheap classify-first probe — the aspect names the work |\n| anything else | *(empty)*, **undeclared** | `PolicyFor` already puts the empty class at the strictest floor |\n| `labels.work_class` misspelled | *(empty)*, **undeclared** | a typo in `routine` must not read as permission to use a laptop |\n\nUndeclared work therefore lands exactly where it landed before this feature existed — on\na vendor — and reports `class-undeclared-conservative` so an operator can see the fix is\na label rather than more hardware. There is no second conservatism mechanism layered on\ntop; one gate in one place is why the behaviour stays predictable. `Complexity` can then\nratchet a floor **up** (a high-complexity `routine` subject becomes `normal-impl`) and\nnever down, which is what makes accepting complexity as an input safe at all.\n\n`Roster.PlaceSubject(subject, candidates)` composes the two and returns the placement\n*and* the classification, so both halves of the decision are visible together.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## Managed-cache posture across launchers\n\nEvery fak launcher that fronts an agent with `fak guard` — `fak accounts launch`,\n`fak codex`, and the dispatch worker (`fak dispatch tick`/`auto`) — can hand guard a\n**managed-cache posture** that decides whether the gateway upgrades the stable-prefix\n`cache_control` breakpoint to the **1h-TTL** tier on the outbound Anthropic wire.\n\n**A subscription seat stays passive by default, on purpose.** Guard's own `--managed-cache\nauto` follows a never-speculate rule: it will not manage a wire whose billing it cannot see.\nA Pro/Max seat authenticates with subscription OAuth (`Authorization: Bearer`), whose cache\neconomics guard does not own, so `auto` resolves **passive** there and no launcher silently\nflips it. Nothing changes for a subscription fleet unless you ask for it.\n\n**An API-key-billed fleet reaches ACTIVE without hand-editing any launcher**, via two env\nknobs read once per launch and threaded onto the child's guard argv (not its environment, so\na *resumed* child — whose gateway env is deliberately stripped — keeps the posture):\n\n| Knob | Effect |\n|------|--------|\n| `FAK_GUARD_API_KEY_ENV=ANTHROPIC_API_KEY` | Names the env var holding the API key. On the Anthropic wire this is the explicit opt-IN to API billing that lets `auto` resolve **ACTIVE** (guard bills the key, not the seat's subscription). |\n| `FAK_MANAGED_CACHE=on\\|off\\|auto` | Forces the posture. `on` upgrades regardless of billing; `off` disables; `auto` (default) defers to the billing signal above. |\n\n`fak accounts launch --managed-cache ` and `fak codex --managed-cache ` expose\nthe same lever per-launch (defaulting to `$FAK_MANAGED_CACHE`); the dispatch worker reads the\nenv knobs only. An unknown mode fails loud at the front-ends; the headless worker warns and\nfalls back to `auto` so a fleet turn never dies over a cache-posture typo. Either way the\nguard child prints the RESOLVED posture in its startup banner, so the worker log witnesses\nwhether the 1h upgrade actually engaged. An unconfigured fleet emits no `--managed-cache`\nflag at all — the guard argv is byte-identical to before this knob existed.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch\n([#2528](https://github.com/anthony-chaudhary/fak/issues/2528)). v1 *resolves and prints*\na binding; it does not yet send a request over the wire to your chosen account. Building\nthe planners from the Targets and running an ensemble's members is the additive dispatch\nwiring, the same shipped-vs-deferred split the routing spine uses for the ensemble fold\nversus its execution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n\n**Placement zones — shipped and deferred, precisely.** *Shipped:* the vocabulary. A\ncompany-operated server is expressible (`kind: fleet`), its account validates on its own\nterms, its route string round-trips to `ZoneFleet`, and `PlacementZone.SelfHosted()`\ngives every downstream consumer one predicate to attribute a token by. Witnessed by\n`TestZonesPartitionTheKnownKinds`, `TestEngineRouteCarriesTheZoneBackOut`,\n`TestShippedExampleRosterCarriesAllThreeZones`, and — at the enforcing floor —\n`TestFleetZoneStaysRemoteAtTheFloor` plus `TestTierOneRouteMirrorAgreesWithTheFloor`.\n*Also shipped:* `Roster.Place` **chooses** a zone (see *Walking the ladder* above).\n*Deferred, and deliberately so:* nothing yet calls it on the live dispatch path, the\nserving path does not yet **record** the zone it used, and the residency floor does not\nyet treat a fleet host as inside a trust boundary. Zone-aware placement, the self-hosted\ntoken-fraction metric, and the escalation ladder are separate, individually reviewable\nchanges on top of this vocabulary — not implied by it. They are tracked as\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416).\n\n### The first consumer: honest stratum attribution\n\n`internal/sessionaudit` classifies audited turns into billing buckets. It used to answer\n\"was this self-hosted?\" from the **model id**, which cannot support the claim: Qwen,\nLlama, Mistral, DeepSeek, GLM and Kimi all publish open weights, so each is servable\neither from hardware you operate or from a vendor's API. That produced two contradictions\nin one file — `deepseek*` was bucketed self-hosted while priced from the published\nDeepSeek *API* rate card, and the roster above binds `qwen/qwen3.6-27b` to **Groq**, a\nvendor, while the classifier called those tokens self-hosted.\n\nA model id now buys only a model-family answer (`open-weights (placement unknown)`), and\nplacement comes from a real signal:\n\n| Call | Answers |\n| --- | --- |\n| `ProviderBucket(model)` | whose *model* it is — never where it ran |\n| `BucketForPlacement(model, zone)` | the bucket, given a `PlacementZone` string |\n| `BucketIsSelfHosted(bucket)` | `(selfHosted, known bool)` — two values on purpose |\n\n`BucketIsSelfHosted` returns a second `known` bool so unattributable volume cannot be\nfolded into either side of a self-hosted fraction. `TestPricedModelsAreNeverBucketedSelfHosted`\nmakes the underlying rule executable and total over the rate card: a model billed per token\nby a third party ran on their hardware, so it can never also be bucketed as self-hosted.\n"}, "e710bc675c6ffd1cc273b6d462d9c19466e4a4b1": {"t": 1785749574.8901966, "n": 24149, "h": "b755b6a229a03aac", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, DeepSeek, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, Groq, DeepSeek, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product sibling of the fleet account switcher (`fak fleet-accounts`,\nthe native Go successor to the legacy `tools/fleet_accounts.py` shim): provider-neutral,\ncredential-safe, and composable with the routing spine. It lives in\n`internal/modelroute/account.go` (pure, stdlib-only, the same package as the routing\ndecision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\nOpenAI-compatible providers such as Groq are represented as `kind: \"openai\"` plus their\nprovider `base_url`, so the wire adapter stays generic while the account id names whose\ncredential is being used.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" },\n { \"id\": \"july6netra_groq\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 1000,\n \"tokens_per_minute\": 8000, \"tokens_per_day\": 200000 },\n { \"id\": \"july6netra_groq_compound\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 250 },\n { \"id\": \"deepseek\", \"kind\": \"deepseek\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 },\n { \"id\": \"deepseek-anthropic\", \"kind\": \"anthropic\",\n \"base_url\": \"https://api.deepseek.com/anthropic\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" },\n { \"model\": \"qwen36-groq\", \"account\": \"july6netra_groq\", \"upstream_model\": \"qwen/qwen3.6-27b\" },\n { \"model\": \"groq-compound\", \"account\": \"july6netra_groq_compound\", \"upstream_model\": \"groq/compound\" },\n { \"model\": \"deepseek-pro\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-pro\" },\n { \"model\": \"deepseek-flash\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-flash\" },\n { \"model\": \"deepseek-chat-compat\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-chat\",\n \"compatibility_only\": true, \"deprecated_after_utc\": \"2026-07-24 15:59 UTC\",\n \"deprecated_alias_for\": \"deepseek-v4-flash non-thinking mode\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nCoding-agent subscriptions add a second layer: **config-home accounts**. Claude uses\n`CLAUDE_CONFIG_DIR` homes such as `~/.claude-gem8-seat`; Codex uses `CODEX_HOME` homes such as\n`~/.codex` or `~/.codex-work`. `fak fleet-accounts` discovers both, derives their non-secret\nprovider-account identity, collapses duplicate homes on the same rate-limit bucket, and offers\nonly homes whose credential state is ready. The default Codex picker profile is\n`gpt-5.6-sol` with `model_reasoning_effort=xhigh`.\n\nThe authored lifecycle registry behind `fak accounts` remains Claude-specific today; its seat\nnames and tombstones are therefore applied only to Claude rows. Codex rows take lifecycle/login\ntruth from their own home (`auth.json`/`config.toml`) and never inherit a same-named Claude seat\nsuch as `default`.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## API-host probe bridge\n\nThe same model-account roster can feed the API-host no-spend probes:\n\n```bash\nfak api-host readiness --from-model-accounts examples/model-accounts.example.json\nfak api-host acceptance --from-model-accounts examples/model-accounts.example.json\n```\n\n`readiness` converts only OpenAI-compatible or local accounts into `/models` probe targets\n(`openai`, `openai-responses`, `xai`, `deepseek`, and `local`). Native provider accounts are not guessed into\nan OpenAI-shaped probe. `acceptance` keeps every account with a probeable base URL visible:\nOpenAI-compatible/local accounts can become `READY_FOR_LIVE_BRIDGE_RUN`, while native\nAnthropic/Gemini accounts are reported as `WIRE_SUPPORTED_UNPROBED`. Model hints come from the\nroster bindings, so the API-host report stays tied to the same abstract model ids the route policy\nuses.\n\nGroq Qwen3.6 uses the OpenAI-compatible base `https://api.groq.com/openai/v1`, credential\nenv var `FAK_GROQ_API_KEY`, and upstream model slug `qwen/qwen3.6-27b`. The example records\nthe current account/model limits as metadata: 30 requests/minute, 1,000 requests/day,\n8,000 tokens/minute, and 200,000 tokens/day. Groq Compound uses the same base and env var\nunder the separate account id `july6netra_groq_compound`, bound as the lower-quality\n`groq-compound` target to upstream slug `groq/compound`; its metadata is request-only:\n30 requests/minute and 250 requests/day, with no token-minute or token-day cap recorded.\nThe Claude dogfood launcher separately caps the outbound `max_tokens` field at 8192 for\n`groq/compound`, because Groq rejects larger per-response output budgets. Those values\nare advisory metadata for routing and launchers; the v1 resolver does not throttle\nrequests.\n\nDeepSeek V4 uses `deepseek-v4-pro` / `deepseek-v4-flash` on the OpenAI-compatible base\n`https://api.deepseek.com`; its Anthropic-compatible profile uses\n`https://api.deepseek.com/anthropic`. The legacy `deepseek-chat` and `deepseek-reasoner`\naliases are accepted only when marked `compatibility_only`, with the documented retirement\nmetadata `2026-07-24 15:59 UTC`, so a new roster does not silently bind to an alias that is\nabout to disappear.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open. The same file pins the tier-1 mirror\n(`modelroute.IsRemoteRoute`) against the enforcing floor over one corpus, because\n`internal/modelroute` sits below `internal/engine` and must keep its own copy of the\non-box family list. See *Placement zones* below for the third rung the binary\nlocal/remote split does not name.\n\n## Placement zones: your box, your company's boxes, someone else's\n\nLocality is not binary. A company that self-hosts runs models in **three** places, and\nan account declares which by its `kind`:\n\n| Zone | `kind` | Where the weights sit | Self-hosted? | On-box? |\n| --- | --- | --- | --- | --- |\n| `device` | `local` | the engineer's own machine (loopback only) | yes | **yes** |\n| `fleet` | `fleet` | a machine the **organization** operates | yes | no |\n| `vendor` | `openai` / `anthropic` / `gemini` / `xai` / `deepseek` / `openai-responses` | a third-party lab's API | no | no |\n\nThe middle rung is the one that carries the token volume in a self-hosting shop — a\nGLM- or Kimi-class open model on company GPUs, shared by every engineer. Before\n`kind: fleet` existed it could not be written down: as `local` it was refused (a local\naccount must carry a loopback base URL), and as `openai` it was admitted but became\nindistinguishable from `api.openai.com` in every downstream record, so the org got no\ncredit for hardware it owns.\n\n```json\n{\n \"id\": \"corp-glm\",\n \"kind\": \"fleet\",\n \"base_url\": \"http://glm.infer.corp.internal:8000/v1\",\n \"label\": \"GLM-5.2 vLLM server the company operates\"\n}\n```\n\nA `fleet` account needs an explicit, non-loopback `http(s)` endpoint (there is no public\ndefault for a host only your org can name, and a loopback address is `device` by\ndefinition — the zones must stay disjoint). Unlike a vendor account it does **not**\nrequire a `cred_env`: an org-operated server on a private network commonly has no API\nkey. Supply one when the endpoint authenticates.\n\n**Self-hosted is not the same as on-box, deliberately.** `ZoneFleet.SelfHosted()` is\ntrue — its tokens count toward \"we ran this on our own silicon\" — but\n`ZoneFleet.OnBox()` is false, so the residency floor still treats a `fleet:…` route as\nremote and still denies a tenant-scoped payload routed there. Naming the zone changes\nwhat fak can *attribute*, not what it *permits*. Letting an org-operated host carry a\nsensitive payload is an enforcement change that needs an operator-declared trust\nboundary (authenticated transport, a named host allowlist), and it is tracked\nseparately — it does not arrive as a side effect of declaring a zone.\n\nThe zones are ordered as an escalation ladder — `device` (0) → `fleet` (1) →\n`vendor` (2) — so \"prefer the cheapest rung that can do the work\" is a comparison on a\ndeclared value rather than a string switch. An unrecognized zone ranks *above* vendor\nand is neither self-hosted nor on-box: unattributable never reads as free or as safe.\n\n### Walking the ladder: `Roster.Place`\n\n`Roster.Place(class, candidates)` is what actually walks it. Given a `WorkClass` and the\nmodels you have bound — each with the work tier you have evidence it can serve — it\nreturns the cheapest rung that can take the job, plus the full ladder walk that produced\nthe answer:\n\n```go\np, err := roster.Place(modelroute.ClassRoutine, []modelroute.Candidate{\n {Model: \"tiny\", Capability: modelroute.TierT2, Measured: true},\n {Model: \"corp-mid\", Capability: modelroute.TierT1, Measured: true},\n {Model: \"frontier\", Capability: modelroute.TierT0, Measured: true},\n})\n// p.Zone == ZoneDevice, p.SelfHosted() == true, p.Escalated == false\n```\n\nTwo rules stop it from being wishful thinking:\n\n**The floor belongs to the work, not to the model.** Admission runs through\n`TierPolicy.Admit`, so a cheap rung cannot take work above its tier however available it\nis. Security/release/destructive work has a floor that never drops to routine, so it\nskips a measured local model and lands on the fleet — `p.Escalated` is true and the\ndevice rung reports `zone-under-tier`. An unrecognized class stays at the strictest floor\nrather than inferring a cheap one.\n\n**Unmeasured capability may not descend the ladder.** `Candidate.Measured` records\nwhether `Capability` is a measurement or an assertion, and an assertion is skipped on\nevery rung below the vendor. This matters right now: `internal/ablate`'s `StubTierScorer`\nmeasures nothing, so a placer that trusted asserted grades would route everything to the\ncheapest rung on a number nobody computed, and would look like it was working. Until\ncapability measurement lands, an operator opts a rung in by marking a candidate\n`Measured` — a claim they can be held to. `TestUnmeasuredCapabilityCannotDescendTheLadder`\npins both halves: an all-asserted ladder places on the vendor, and the same work moves to\nthe device the moment the cheap rung is measured.\n\nEvery rung is reported, including the ones passed over, in a closed reason vocabulary\n(`placed-in-zone`, `zone-under-tier`, `zone-capability-unmeasured`, `zone-no-candidate`,\n`zone-not-reached`, `escalated-past-cheaper-zone`) — because \"why is this *still* going to\na vendor?\" is the question an operator actually asks. A candidate the roster cannot\nresolve is a loud error, never a silent skip: a typo in a placement config must surface as\na misconfiguration rather than as traffic quietly continuing to bill a vendor.\n\nThe trap this code is written around is that `WorkTier` numbers are **inverted** — T0 is\nthe most demanding but the lowest number. Every comparison goes through\n`WorkTier.MeetsRequirement`, never a raw `<` or `>=`. Substituting the natural-looking\n`capability >= required` fails four tests at once, and the way it fails is the point: a\n4B laptop model takes both the ultra-hard and the security/destructive work.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## Managed-cache posture across launchers\n\nEvery fak launcher that fronts an agent with `fak guard` — `fak accounts launch`,\n`fak codex`, and the dispatch worker (`fak dispatch tick`/`auto`) — can hand guard a\n**managed-cache posture** that decides whether the gateway upgrades the stable-prefix\n`cache_control` breakpoint to the **1h-TTL** tier on the outbound Anthropic wire.\n\n**A subscription seat stays passive by default, on purpose.** Guard's own `--managed-cache\nauto` follows a never-speculate rule: it will not manage a wire whose billing it cannot see.\nA Pro/Max seat authenticates with subscription OAuth (`Authorization: Bearer`), whose cache\neconomics guard does not own, so `auto` resolves **passive** there and no launcher silently\nflips it. Nothing changes for a subscription fleet unless you ask for it.\n\n**An API-key-billed fleet reaches ACTIVE without hand-editing any launcher**, via two env\nknobs read once per launch and threaded onto the child's guard argv (not its environment, so\na *resumed* child — whose gateway env is deliberately stripped — keeps the posture):\n\n| Knob | Effect |\n|------|--------|\n| `FAK_GUARD_API_KEY_ENV=ANTHROPIC_API_KEY` | Names the env var holding the API key. On the Anthropic wire this is the explicit opt-IN to API billing that lets `auto` resolve **ACTIVE** (guard bills the key, not the seat's subscription). |\n| `FAK_MANAGED_CACHE=on\\|off\\|auto` | Forces the posture. `on` upgrades regardless of billing; `off` disables; `auto` (default) defers to the billing signal above. |\n\n`fak accounts launch --managed-cache ` and `fak codex --managed-cache ` expose\nthe same lever per-launch (defaulting to `$FAK_MANAGED_CACHE`); the dispatch worker reads the\nenv knobs only. An unknown mode fails loud at the front-ends; the headless worker warns and\nfalls back to `auto` so a fleet turn never dies over a cache-posture typo. Either way the\nguard child prints the RESOLVED posture in its startup banner, so the worker log witnesses\nwhether the 1h upgrade actually engaged. An unconfigured fleet emits no `--managed-cache`\nflag at all — the guard argv is byte-identical to before this knob existed.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch\n([#2528](https://github.com/anthony-chaudhary/fak/issues/2528)). v1 *resolves and prints*\na binding; it does not yet send a request over the wire to your chosen account. Building\nthe planners from the Targets and running an ensemble's members is the additive dispatch\nwiring, the same shipped-vs-deferred split the routing spine uses for the ensemble fold\nversus its execution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n\n**Placement zones — shipped and deferred, precisely.** *Shipped:* the vocabulary. A\ncompany-operated server is expressible (`kind: fleet`), its account validates on its own\nterms, its route string round-trips to `ZoneFleet`, and `PlacementZone.SelfHosted()`\ngives every downstream consumer one predicate to attribute a token by. Witnessed by\n`TestZonesPartitionTheKnownKinds`, `TestEngineRouteCarriesTheZoneBackOut`,\n`TestShippedExampleRosterCarriesAllThreeZones`, and — at the enforcing floor —\n`TestFleetZoneStaysRemoteAtTheFloor` plus `TestTierOneRouteMirrorAgreesWithTheFloor`.\n*Also shipped:* `Roster.Place` **chooses** a zone (see *Walking the ladder* above).\n*Deferred, and deliberately so:* nothing yet calls it on the live dispatch path, the\nserving path does not yet **record** the zone it used, and the residency floor does not\nyet treat a fleet host as inside a trust boundary. Zone-aware placement, the self-hosted\ntoken-fraction metric, and the escalation ladder are separate, individually reviewable\nchanges on top of this vocabulary — not implied by it. They are tracked as\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416).\n\n### The first consumer: honest stratum attribution\n\n`internal/sessionaudit` classifies audited turns into billing buckets. It used to answer\n\"was this self-hosted?\" from the **model id**, which cannot support the claim: Qwen,\nLlama, Mistral, DeepSeek, GLM and Kimi all publish open weights, so each is servable\neither from hardware you operate or from a vendor's API. That produced two contradictions\nin one file — `deepseek*` was bucketed self-hosted while priced from the published\nDeepSeek *API* rate card, and the roster above binds `qwen/qwen3.6-27b` to **Groq**, a\nvendor, while the classifier called those tokens self-hosted.\n\nA model id now buys only a model-family answer (`open-weights (placement unknown)`), and\nplacement comes from a real signal:\n\n| Call | Answers |\n| --- | --- |\n| `ProviderBucket(model)` | whose *model* it is — never where it ran |\n| `BucketForPlacement(model, zone)` | the bucket, given a `PlacementZone` string |\n| `BucketIsSelfHosted(bucket)` | `(selfHosted, known bool)` — two values on purpose |\n\n`BucketIsSelfHosted` returns a second `known` bool so unattributable volume cannot be\nfolded into either side of a self-hosted fraction. `TestPricedModelsAreNeverBucketedSelfHosted`\nmakes the underlying rule executable and total over the rate card: a model billed per token\nby a third party ran on their hardware, so it can never also be bucketed as self-hosted.\n"}, "fb3c91ebb9f5241080a5ad30c65b45e0d0e6f371": {"t": 1785749576.3806374, "n": 21188, "h": "f8968b3fc7790a7d", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, DeepSeek, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, Groq, DeepSeek, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product sibling of the fleet account switcher (`fak fleet-accounts`,\nthe native Go successor to the legacy `tools/fleet_accounts.py` shim): provider-neutral,\ncredential-safe, and composable with the routing spine. It lives in\n`internal/modelroute/account.go` (pure, stdlib-only, the same package as the routing\ndecision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\nOpenAI-compatible providers such as Groq are represented as `kind: \"openai\"` plus their\nprovider `base_url`, so the wire adapter stays generic while the account id names whose\ncredential is being used.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" },\n { \"id\": \"july6netra_groq\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 1000,\n \"tokens_per_minute\": 8000, \"tokens_per_day\": 200000 },\n { \"id\": \"july6netra_groq_compound\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 250 },\n { \"id\": \"deepseek\", \"kind\": \"deepseek\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 },\n { \"id\": \"deepseek-anthropic\", \"kind\": \"anthropic\",\n \"base_url\": \"https://api.deepseek.com/anthropic\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" },\n { \"model\": \"qwen36-groq\", \"account\": \"july6netra_groq\", \"upstream_model\": \"qwen/qwen3.6-27b\" },\n { \"model\": \"groq-compound\", \"account\": \"july6netra_groq_compound\", \"upstream_model\": \"groq/compound\" },\n { \"model\": \"deepseek-pro\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-pro\" },\n { \"model\": \"deepseek-flash\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-flash\" },\n { \"model\": \"deepseek-chat-compat\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-chat\",\n \"compatibility_only\": true, \"deprecated_after_utc\": \"2026-07-24 15:59 UTC\",\n \"deprecated_alias_for\": \"deepseek-v4-flash non-thinking mode\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nCoding-agent subscriptions add a second layer: **config-home accounts**. Claude uses\n`CLAUDE_CONFIG_DIR` homes such as `~/.claude-gem8-seat`; Codex uses `CODEX_HOME` homes such as\n`~/.codex` or `~/.codex-work`. `fak fleet-accounts` discovers both, derives their non-secret\nprovider-account identity, collapses duplicate homes on the same rate-limit bucket, and offers\nonly homes whose credential state is ready. The default Codex picker profile is\n`gpt-5.6-sol` with `model_reasoning_effort=xhigh`.\n\nThe authored lifecycle registry behind `fak accounts` remains Claude-specific today; its seat\nnames and tombstones are therefore applied only to Claude rows. Codex rows take lifecycle/login\ntruth from their own home (`auth.json`/`config.toml`) and never inherit a same-named Claude seat\nsuch as `default`.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## API-host probe bridge\n\nThe same model-account roster can feed the API-host no-spend probes:\n\n```bash\nfak api-host readiness --from-model-accounts examples/model-accounts.example.json\nfak api-host acceptance --from-model-accounts examples/model-accounts.example.json\n```\n\n`readiness` converts only OpenAI-compatible or local accounts into `/models` probe targets\n(`openai`, `openai-responses`, `xai`, `deepseek`, and `local`). Native provider accounts are not guessed into\nan OpenAI-shaped probe. `acceptance` keeps every account with a probeable base URL visible:\nOpenAI-compatible/local accounts can become `READY_FOR_LIVE_BRIDGE_RUN`, while native\nAnthropic/Gemini accounts are reported as `WIRE_SUPPORTED_UNPROBED`. Model hints come from the\nroster bindings, so the API-host report stays tied to the same abstract model ids the route policy\nuses.\n\nGroq Qwen3.6 uses the OpenAI-compatible base `https://api.groq.com/openai/v1`, credential\nenv var `FAK_GROQ_API_KEY`, and upstream model slug `qwen/qwen3.6-27b`. The example records\nthe current account/model limits as metadata: 30 requests/minute, 1,000 requests/day,\n8,000 tokens/minute, and 200,000 tokens/day. Groq Compound uses the same base and env var\nunder the separate account id `july6netra_groq_compound`, bound as the lower-quality\n`groq-compound` target to upstream slug `groq/compound`; its metadata is request-only:\n30 requests/minute and 250 requests/day, with no token-minute or token-day cap recorded.\nThe Claude dogfood launcher separately caps the outbound `max_tokens` field at 8192 for\n`groq/compound`, because Groq rejects larger per-response output budgets. Those values\nare advisory metadata for routing and launchers; the v1 resolver does not throttle\nrequests.\n\nDeepSeek V4 uses `deepseek-v4-pro` / `deepseek-v4-flash` on the OpenAI-compatible base\n`https://api.deepseek.com`; its Anthropic-compatible profile uses\n`https://api.deepseek.com/anthropic`. The legacy `deepseek-chat` and `deepseek-reasoner`\naliases are accepted only when marked `compatibility_only`, with the documented retirement\nmetadata `2026-07-24 15:59 UTC`, so a new roster does not silently bind to an alias that is\nabout to disappear.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open. The same file pins the tier-1 mirror\n(`modelroute.IsRemoteRoute`) against the enforcing floor over one corpus, because\n`internal/modelroute` sits below `internal/engine` and must keep its own copy of the\non-box family list. See *Placement zones* below for the third rung the binary\nlocal/remote split does not name.\n\n## Placement zones: your box, your company's boxes, someone else's\n\nLocality is not binary. A company that self-hosts runs models in **three** places, and\nan account declares which by its `kind`:\n\n| Zone | `kind` | Where the weights sit | Self-hosted? | On-box? |\n| --- | --- | --- | --- | --- |\n| `device` | `local` | the engineer's own machine (loopback only) | yes | **yes** |\n| `fleet` | `fleet` | a machine the **organization** operates | yes | no |\n| `vendor` | `openai` / `anthropic` / `gemini` / `xai` / `deepseek` / `openai-responses` | a third-party lab's API | no | no |\n\nThe middle rung is the one that carries the token volume in a self-hosting shop — a\nGLM- or Kimi-class open model on company GPUs, shared by every engineer. Before\n`kind: fleet` existed it could not be written down: as `local` it was refused (a local\naccount must carry a loopback base URL), and as `openai` it was admitted but became\nindistinguishable from `api.openai.com` in every downstream record, so the org got no\ncredit for hardware it owns.\n\n```json\n{\n \"id\": \"corp-glm\",\n \"kind\": \"fleet\",\n \"base_url\": \"http://glm.infer.corp.internal:8000/v1\",\n \"label\": \"GLM-5.2 vLLM server the company operates\"\n}\n```\n\nA `fleet` account needs an explicit, non-loopback `http(s)` endpoint (there is no public\ndefault for a host only your org can name, and a loopback address is `device` by\ndefinition — the zones must stay disjoint). Unlike a vendor account it does **not**\nrequire a `cred_env`: an org-operated server on a private network commonly has no API\nkey. Supply one when the endpoint authenticates.\n\n**Self-hosted is not the same as on-box, deliberately.** `ZoneFleet.SelfHosted()` is\ntrue — its tokens count toward \"we ran this on our own silicon\" — but\n`ZoneFleet.OnBox()` is false, so the residency floor still treats a `fleet:…` route as\nremote and still denies a tenant-scoped payload routed there. Naming the zone changes\nwhat fak can *attribute*, not what it *permits*. Letting an org-operated host carry a\nsensitive payload is an enforcement change that needs an operator-declared trust\nboundary (authenticated transport, a named host allowlist), and it is tracked\nseparately — it does not arrive as a side effect of declaring a zone.\n\nThe zones are ordered as an escalation ladder — `device` (0) → `fleet` (1) →\n`vendor` (2) — so \"prefer the cheapest rung that can do the work\" is a comparison on a\ndeclared value rather than a string switch. An unrecognized zone ranks *above* vendor\nand is neither self-hosted nor on-box: unattributable never reads as free or as safe.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## Managed-cache posture across launchers\n\nEvery fak launcher that fronts an agent with `fak guard` — `fak accounts launch`,\n`fak codex`, and the dispatch worker (`fak dispatch tick`/`auto`) — can hand guard a\n**managed-cache posture** that decides whether the gateway upgrades the stable-prefix\n`cache_control` breakpoint to the **1h-TTL** tier on the outbound Anthropic wire.\n\n**A subscription seat stays passive by default, on purpose.** Guard's own `--managed-cache\nauto` follows a never-speculate rule: it will not manage a wire whose billing it cannot see.\nA Pro/Max seat authenticates with subscription OAuth (`Authorization: Bearer`), whose cache\neconomics guard does not own, so `auto` resolves **passive** there and no launcher silently\nflips it. Nothing changes for a subscription fleet unless you ask for it.\n\n**An API-key-billed fleet reaches ACTIVE without hand-editing any launcher**, via two env\nknobs read once per launch and threaded onto the child's guard argv (not its environment, so\na *resumed* child — whose gateway env is deliberately stripped — keeps the posture):\n\n| Knob | Effect |\n|------|--------|\n| `FAK_GUARD_API_KEY_ENV=ANTHROPIC_API_KEY` | Names the env var holding the API key. On the Anthropic wire this is the explicit opt-IN to API billing that lets `auto` resolve **ACTIVE** (guard bills the key, not the seat's subscription). |\n| `FAK_MANAGED_CACHE=on\\|off\\|auto` | Forces the posture. `on` upgrades regardless of billing; `off` disables; `auto` (default) defers to the billing signal above. |\n\n`fak accounts launch --managed-cache ` and `fak codex --managed-cache ` expose\nthe same lever per-launch (defaulting to `$FAK_MANAGED_CACHE`); the dispatch worker reads the\nenv knobs only. An unknown mode fails loud at the front-ends; the headless worker warns and\nfalls back to `auto` so a fleet turn never dies over a cache-posture typo. Either way the\nguard child prints the RESOLVED posture in its startup banner, so the worker log witnesses\nwhether the 1h upgrade actually engaged. An unconfigured fleet emits no `--managed-cache`\nflag at all — the guard argv is byte-identical to before this knob existed.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch\n([#2528](https://github.com/anthony-chaudhary/fak/issues/2528)). v1 *resolves and prints*\na binding; it does not yet send a request over the wire to your chosen account. Building\nthe planners from the Targets and running an ensemble's members is the additive dispatch\nwiring, the same shipped-vs-deferred split the routing spine uses for the ensemble fold\nversus its execution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n\n**Placement zones — shipped and deferred, precisely.** *Shipped:* the vocabulary. A\ncompany-operated server is expressible (`kind: fleet`), its account validates on its own\nterms, its route string round-trips to `ZoneFleet`, and `PlacementZone.SelfHosted()`\ngives every downstream consumer one predicate to attribute a token by. Witnessed by\n`TestZonesPartitionTheKnownKinds`, `TestEngineRouteCarriesTheZoneBackOut`,\n`TestShippedExampleRosterCarriesAllThreeZones`, and — at the enforcing floor —\n`TestFleetZoneStaysRemoteAtTheFloor` plus `TestTierOneRouteMirrorAgreesWithTheFloor`.\n*Deferred, and deliberately so:* nothing yet **chooses** a zone automatically, the serving\npath does not yet **record** the zone it used, and the residency floor does not yet treat\na fleet host as inside a trust boundary. Zone-aware placement, the self-hosted\ntoken-fraction metric, and the escalation ladder are separate, individually reviewable\nchanges on top of this vocabulary — not implied by it. They are tracked as\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416).\n\n### The first consumer: honest stratum attribution\n\n`internal/sessionaudit` classifies audited turns into billing buckets. It used to answer\n\"was this self-hosted?\" from the **model id**, which cannot support the claim: Qwen,\nLlama, Mistral, DeepSeek, GLM and Kimi all publish open weights, so each is servable\neither from hardware you operate or from a vendor's API. That produced two contradictions\nin one file — `deepseek*` was bucketed self-hosted while priced from the published\nDeepSeek *API* rate card, and the roster above binds `qwen/qwen3.6-27b` to **Groq**, a\nvendor, while the classifier called those tokens self-hosted.\n\nA model id now buys only a model-family answer (`open-weights (placement unknown)`), and\nplacement comes from a real signal:\n\n| Call | Answers |\n| --- | --- |\n| `ProviderBucket(model)` | whose *model* it is — never where it ran |\n| `BucketForPlacement(model, zone)` | the bucket, given a `PlacementZone` string |\n| `BucketIsSelfHosted(bucket)` | `(selfHosted, known bool)` — two values on purpose |\n\n`BucketIsSelfHosted` returns a second `known` bool so unattributable volume cannot be\nfolded into either side of a self-hosted fraction. `TestPricedModelsAreNeverBucketedSelfHosted`\nmakes the underlying rule executable and total over the rate card: a model billed per token\nby a third party ran on their hardware, so it can never also be bucketed as self-hosted.\n"}, "c36eabceff08189eb975f8a168e2a44ee7474410": {"t": 1785749577.6572297, "n": 19685, "h": "1fbf6fd1ff4a7691", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, DeepSeek, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, Groq, DeepSeek, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product sibling of the fleet account switcher (`fak fleet-accounts`,\nthe native Go successor to the legacy `tools/fleet_accounts.py` shim): provider-neutral,\ncredential-safe, and composable with the routing spine. It lives in\n`internal/modelroute/account.go` (pure, stdlib-only, the same package as the routing\ndecision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\nOpenAI-compatible providers such as Groq are represented as `kind: \"openai\"` plus their\nprovider `base_url`, so the wire adapter stays generic while the account id names whose\ncredential is being used.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" },\n { \"id\": \"july6netra_groq\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 1000,\n \"tokens_per_minute\": 8000, \"tokens_per_day\": 200000 },\n { \"id\": \"july6netra_groq_compound\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 250 },\n { \"id\": \"deepseek\", \"kind\": \"deepseek\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 },\n { \"id\": \"deepseek-anthropic\", \"kind\": \"anthropic\",\n \"base_url\": \"https://api.deepseek.com/anthropic\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" },\n { \"model\": \"qwen36-groq\", \"account\": \"july6netra_groq\", \"upstream_model\": \"qwen/qwen3.6-27b\" },\n { \"model\": \"groq-compound\", \"account\": \"july6netra_groq_compound\", \"upstream_model\": \"groq/compound\" },\n { \"model\": \"deepseek-pro\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-pro\" },\n { \"model\": \"deepseek-flash\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-flash\" },\n { \"model\": \"deepseek-chat-compat\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-chat\",\n \"compatibility_only\": true, \"deprecated_after_utc\": \"2026-07-24 15:59 UTC\",\n \"deprecated_alias_for\": \"deepseek-v4-flash non-thinking mode\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nCoding-agent subscriptions add a second layer: **config-home accounts**. Claude uses\n`CLAUDE_CONFIG_DIR` homes such as `~/.claude-gem8-seat`; Codex uses `CODEX_HOME` homes such as\n`~/.codex` or `~/.codex-work`. `fak fleet-accounts` discovers both, derives their non-secret\nprovider-account identity, collapses duplicate homes on the same rate-limit bucket, and offers\nonly homes whose credential state is ready. The default Codex picker profile is\n`gpt-5.6-sol` with `model_reasoning_effort=xhigh`.\n\nThe authored lifecycle registry behind `fak accounts` remains Claude-specific today; its seat\nnames and tombstones are therefore applied only to Claude rows. Codex rows take lifecycle/login\ntruth from their own home (`auth.json`/`config.toml`) and never inherit a same-named Claude seat\nsuch as `default`.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## API-host probe bridge\n\nThe same model-account roster can feed the API-host no-spend probes:\n\n```bash\nfak api-host readiness --from-model-accounts examples/model-accounts.example.json\nfak api-host acceptance --from-model-accounts examples/model-accounts.example.json\n```\n\n`readiness` converts only OpenAI-compatible or local accounts into `/models` probe targets\n(`openai`, `openai-responses`, `xai`, `deepseek`, and `local`). Native provider accounts are not guessed into\nan OpenAI-shaped probe. `acceptance` keeps every account with a probeable base URL visible:\nOpenAI-compatible/local accounts can become `READY_FOR_LIVE_BRIDGE_RUN`, while native\nAnthropic/Gemini accounts are reported as `WIRE_SUPPORTED_UNPROBED`. Model hints come from the\nroster bindings, so the API-host report stays tied to the same abstract model ids the route policy\nuses.\n\nGroq Qwen3.6 uses the OpenAI-compatible base `https://api.groq.com/openai/v1`, credential\nenv var `FAK_GROQ_API_KEY`, and upstream model slug `qwen/qwen3.6-27b`. The example records\nthe current account/model limits as metadata: 30 requests/minute, 1,000 requests/day,\n8,000 tokens/minute, and 200,000 tokens/day. Groq Compound uses the same base and env var\nunder the separate account id `july6netra_groq_compound`, bound as the lower-quality\n`groq-compound` target to upstream slug `groq/compound`; its metadata is request-only:\n30 requests/minute and 250 requests/day, with no token-minute or token-day cap recorded.\nThe Claude dogfood launcher separately caps the outbound `max_tokens` field at 8192 for\n`groq/compound`, because Groq rejects larger per-response output budgets. Those values\nare advisory metadata for routing and launchers; the v1 resolver does not throttle\nrequests.\n\nDeepSeek V4 uses `deepseek-v4-pro` / `deepseek-v4-flash` on the OpenAI-compatible base\n`https://api.deepseek.com`; its Anthropic-compatible profile uses\n`https://api.deepseek.com/anthropic`. The legacy `deepseek-chat` and `deepseek-reasoner`\naliases are accepted only when marked `compatibility_only`, with the documented retirement\nmetadata `2026-07-24 15:59 UTC`, so a new roster does not silently bind to an alias that is\nabout to disappear.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open. The same file pins the tier-1 mirror\n(`modelroute.IsRemoteRoute`) against the enforcing floor over one corpus, because\n`internal/modelroute` sits below `internal/engine` and must keep its own copy of the\non-box family list. See *Placement zones* below for the third rung the binary\nlocal/remote split does not name.\n\n## Placement zones: your box, your company's boxes, someone else's\n\nLocality is not binary. A company that self-hosts runs models in **three** places, and\nan account declares which by its `kind`:\n\n| Zone | `kind` | Where the weights sit | Self-hosted? | On-box? |\n| --- | --- | --- | --- | --- |\n| `device` | `local` | the engineer's own machine (loopback only) | yes | **yes** |\n| `fleet` | `fleet` | a machine the **organization** operates | yes | no |\n| `vendor` | `openai` / `anthropic` / `gemini` / `xai` / `deepseek` / `openai-responses` | a third-party lab's API | no | no |\n\nThe middle rung is the one that carries the token volume in a self-hosting shop — a\nGLM- or Kimi-class open model on company GPUs, shared by every engineer. Before\n`kind: fleet` existed it could not be written down: as `local` it was refused (a local\naccount must carry a loopback base URL), and as `openai` it was admitted but became\nindistinguishable from `api.openai.com` in every downstream record, so the org got no\ncredit for hardware it owns.\n\n```json\n{\n \"id\": \"corp-glm\",\n \"kind\": \"fleet\",\n \"base_url\": \"http://glm.infer.corp.internal:8000/v1\",\n \"label\": \"GLM-5.2 vLLM server the company operates\"\n}\n```\n\nA `fleet` account needs an explicit, non-loopback `http(s)` endpoint (there is no public\ndefault for a host only your org can name, and a loopback address is `device` by\ndefinition — the zones must stay disjoint). Unlike a vendor account it does **not**\nrequire a `cred_env`: an org-operated server on a private network commonly has no API\nkey. Supply one when the endpoint authenticates.\n\n**Self-hosted is not the same as on-box, deliberately.** `ZoneFleet.SelfHosted()` is\ntrue — its tokens count toward \"we ran this on our own silicon\" — but\n`ZoneFleet.OnBox()` is false, so the residency floor still treats a `fleet:…` route as\nremote and still denies a tenant-scoped payload routed there. Naming the zone changes\nwhat fak can *attribute*, not what it *permits*. Letting an org-operated host carry a\nsensitive payload is an enforcement change that needs an operator-declared trust\nboundary (authenticated transport, a named host allowlist), and it is tracked\nseparately — it does not arrive as a side effect of declaring a zone.\n\nThe zones are ordered as an escalation ladder — `device` (0) → `fleet` (1) →\n`vendor` (2) — so \"prefer the cheapest rung that can do the work\" is a comparison on a\ndeclared value rather than a string switch. An unrecognized zone ranks *above* vendor\nand is neither self-hosted nor on-box: unattributable never reads as free or as safe.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## Managed-cache posture across launchers\n\nEvery fak launcher that fronts an agent with `fak guard` — `fak accounts launch`,\n`fak codex`, and the dispatch worker (`fak dispatch tick`/`auto`) — can hand guard a\n**managed-cache posture** that decides whether the gateway upgrades the stable-prefix\n`cache_control` breakpoint to the **1h-TTL** tier on the outbound Anthropic wire.\n\n**A subscription seat stays passive by default, on purpose.** Guard's own `--managed-cache\nauto` follows a never-speculate rule: it will not manage a wire whose billing it cannot see.\nA Pro/Max seat authenticates with subscription OAuth (`Authorization: Bearer`), whose cache\neconomics guard does not own, so `auto` resolves **passive** there and no launcher silently\nflips it. Nothing changes for a subscription fleet unless you ask for it.\n\n**An API-key-billed fleet reaches ACTIVE without hand-editing any launcher**, via two env\nknobs read once per launch and threaded onto the child's guard argv (not its environment, so\na *resumed* child — whose gateway env is deliberately stripped — keeps the posture):\n\n| Knob | Effect |\n|------|--------|\n| `FAK_GUARD_API_KEY_ENV=ANTHROPIC_API_KEY` | Names the env var holding the API key. On the Anthropic wire this is the explicit opt-IN to API billing that lets `auto` resolve **ACTIVE** (guard bills the key, not the seat's subscription). |\n| `FAK_MANAGED_CACHE=on\\|off\\|auto` | Forces the posture. `on` upgrades regardless of billing; `off` disables; `auto` (default) defers to the billing signal above. |\n\n`fak accounts launch --managed-cache ` and `fak codex --managed-cache ` expose\nthe same lever per-launch (defaulting to `$FAK_MANAGED_CACHE`); the dispatch worker reads the\nenv knobs only. An unknown mode fails loud at the front-ends; the headless worker warns and\nfalls back to `auto` so a fleet turn never dies over a cache-posture typo. Either way the\nguard child prints the RESOLVED posture in its startup banner, so the worker log witnesses\nwhether the 1h upgrade actually engaged. An unconfigured fleet emits no `--managed-cache`\nflag at all — the guard argv is byte-identical to before this knob existed.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch\n([#2528](https://github.com/anthony-chaudhary/fak/issues/2528)). v1 *resolves and prints*\na binding; it does not yet send a request over the wire to your chosen account. Building\nthe planners from the Targets and running an ensemble's members is the additive dispatch\nwiring, the same shipped-vs-deferred split the routing spine uses for the ensemble fold\nversus its execution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n\n**Placement zones — shipped and deferred, precisely.** *Shipped:* the vocabulary. A\ncompany-operated server is expressible (`kind: fleet`), its account validates on its own\nterms, its route string round-trips to `ZoneFleet`, and `PlacementZone.SelfHosted()`\ngives every downstream consumer one predicate to attribute a token by. Witnessed by\n`TestZonesPartitionTheKnownKinds`, `TestEngineRouteCarriesTheZoneBackOut`,\n`TestShippedExampleRosterCarriesAllThreeZones`, and — at the enforcing floor —\n`TestFleetZoneStaysRemoteAtTheFloor` plus `TestTierOneRouteMirrorAgreesWithTheFloor`.\n*Deferred, and deliberately so:* nothing yet **chooses** a zone automatically, no usage\nrecord yet **carries** the zone, and the residency floor does not yet treat a fleet host\nas inside a trust boundary. Zone-aware placement, the self-hosted token-fraction metric,\nand the escalation ladder are separate, individually reviewable changes on top of this\nvocabulary — not implied by it.\n"}, "67277ad1f45055556fba473650d76de9ad19d5bc": {"t": 1785749578.783332, "n": 15830, "h": "d9a8134f3e4100c2", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, DeepSeek, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, Groq, DeepSeek, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product sibling of the fleet account switcher (`fak fleet-accounts`,\nthe native Go successor to the legacy `tools/fleet_accounts.py` shim): provider-neutral,\ncredential-safe, and composable with the routing spine. It lives in\n`internal/modelroute/account.go` (pure, stdlib-only, the same package as the routing\ndecision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\nOpenAI-compatible providers such as Groq are represented as `kind: \"openai\"` plus their\nprovider `base_url`, so the wire adapter stays generic while the account id names whose\ncredential is being used.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" },\n { \"id\": \"july6netra_groq\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 1000,\n \"tokens_per_minute\": 8000, \"tokens_per_day\": 200000 },\n { \"id\": \"july6netra_groq_compound\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 250 },\n { \"id\": \"deepseek\", \"kind\": \"deepseek\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 },\n { \"id\": \"deepseek-anthropic\", \"kind\": \"anthropic\",\n \"base_url\": \"https://api.deepseek.com/anthropic\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" },\n { \"model\": \"qwen36-groq\", \"account\": \"july6netra_groq\", \"upstream_model\": \"qwen/qwen3.6-27b\" },\n { \"model\": \"groq-compound\", \"account\": \"july6netra_groq_compound\", \"upstream_model\": \"groq/compound\" },\n { \"model\": \"deepseek-pro\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-pro\" },\n { \"model\": \"deepseek-flash\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-flash\" },\n { \"model\": \"deepseek-chat-compat\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-chat\",\n \"compatibility_only\": true, \"deprecated_after_utc\": \"2026-07-24 15:59 UTC\",\n \"deprecated_alias_for\": \"deepseek-v4-flash non-thinking mode\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nCoding-agent subscriptions add a second layer: **config-home accounts**. Claude uses\n`CLAUDE_CONFIG_DIR` homes such as `~/.claude-gem8-seat`; Codex uses `CODEX_HOME` homes such as\n`~/.codex` or `~/.codex-work`. `fak fleet-accounts` discovers both, derives their non-secret\nprovider-account identity, collapses duplicate homes on the same rate-limit bucket, and offers\nonly homes whose credential state is ready. The default Codex picker profile is\n`gpt-5.6-sol` with `model_reasoning_effort=xhigh`.\n\nThe authored lifecycle registry behind `fak accounts` remains Claude-specific today; its seat\nnames and tombstones are therefore applied only to Claude rows. Codex rows take lifecycle/login\ntruth from their own home (`auth.json`/`config.toml`) and never inherit a same-named Claude seat\nsuch as `default`.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## API-host probe bridge\n\nThe same model-account roster can feed the API-host no-spend probes:\n\n```bash\nfak api-host readiness --from-model-accounts examples/model-accounts.example.json\nfak api-host acceptance --from-model-accounts examples/model-accounts.example.json\n```\n\n`readiness` converts only OpenAI-compatible or local accounts into `/models` probe targets\n(`openai`, `openai-responses`, `xai`, `deepseek`, and `local`). Native provider accounts are not guessed into\nan OpenAI-shaped probe. `acceptance` keeps every account with a probeable base URL visible:\nOpenAI-compatible/local accounts can become `READY_FOR_LIVE_BRIDGE_RUN`, while native\nAnthropic/Gemini accounts are reported as `WIRE_SUPPORTED_UNPROBED`. Model hints come from the\nroster bindings, so the API-host report stays tied to the same abstract model ids the route policy\nuses.\n\nGroq Qwen3.6 uses the OpenAI-compatible base `https://api.groq.com/openai/v1`, credential\nenv var `FAK_GROQ_API_KEY`, and upstream model slug `qwen/qwen3.6-27b`. The example records\nthe current account/model limits as metadata: 30 requests/minute, 1,000 requests/day,\n8,000 tokens/minute, and 200,000 tokens/day. Groq Compound uses the same base and env var\nunder the separate account id `july6netra_groq_compound`, bound as the lower-quality\n`groq-compound` target to upstream slug `groq/compound`; its metadata is request-only:\n30 requests/minute and 250 requests/day, with no token-minute or token-day cap recorded.\nThe Claude dogfood launcher separately caps the outbound `max_tokens` field at 8192 for\n`groq/compound`, because Groq rejects larger per-response output budgets. Those values\nare advisory metadata for routing and launchers; the v1 resolver does not throttle\nrequests.\n\nDeepSeek V4 uses `deepseek-v4-pro` / `deepseek-v4-flash` on the OpenAI-compatible base\n`https://api.deepseek.com`; its Anthropic-compatible profile uses\n`https://api.deepseek.com/anthropic`. The legacy `deepseek-chat` and `deepseek-reasoner`\naliases are accepted only when marked `compatibility_only`, with the documented retirement\nmetadata `2026-07-24 15:59 UTC`, so a new roster does not silently bind to an alias that is\nabout to disappear.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## Managed-cache posture across launchers\n\nEvery fak launcher that fronts an agent with `fak guard` — `fak accounts launch`,\n`fak codex`, and the dispatch worker (`fak dispatch tick`/`auto`) — can hand guard a\n**managed-cache posture** that decides whether the gateway upgrades the stable-prefix\n`cache_control` breakpoint to the **1h-TTL** tier on the outbound Anthropic wire.\n\n**A subscription seat stays passive by default, on purpose.** Guard's own `--managed-cache\nauto` follows a never-speculate rule: it will not manage a wire whose billing it cannot see.\nA Pro/Max seat authenticates with subscription OAuth (`Authorization: Bearer`), whose cache\neconomics guard does not own, so `auto` resolves **passive** there and no launcher silently\nflips it. Nothing changes for a subscription fleet unless you ask for it.\n\n**An API-key-billed fleet reaches ACTIVE without hand-editing any launcher**, via two env\nknobs read once per launch and threaded onto the child's guard argv (not its environment, so\na *resumed* child — whose gateway env is deliberately stripped — keeps the posture):\n\n| Knob | Effect |\n|------|--------|\n| `FAK_GUARD_API_KEY_ENV=ANTHROPIC_API_KEY` | Names the env var holding the API key. On the Anthropic wire this is the explicit opt-IN to API billing that lets `auto` resolve **ACTIVE** (guard bills the key, not the seat's subscription). |\n| `FAK_MANAGED_CACHE=on\\|off\\|auto` | Forces the posture. `on` upgrades regardless of billing; `off` disables; `auto` (default) defers to the billing signal above. |\n\n`fak accounts launch --managed-cache ` and `fak codex --managed-cache ` expose\nthe same lever per-launch (defaulting to `$FAK_MANAGED_CACHE`); the dispatch worker reads the\nenv knobs only. An unknown mode fails loud at the front-ends; the headless worker warns and\nfalls back to `auto` so a fleet turn never dies over a cache-posture typo. Either way the\nguard child prints the RESOLVED posture in its startup banner, so the worker log witnesses\nwhether the 1h upgrade actually engaged. An unconfigured fleet emits no `--managed-cache`\nflag at all — the guard argv is byte-identical to before this knob existed.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch\n([#2528](https://github.com/anthony-chaudhary/fak/issues/2528)). v1 *resolves and prints*\na binding; it does not yet send a request over the wire to your chosen account. Building\nthe planners from the Targets and running an ensemble's members is the additive dispatch\nwiring, the same shipped-vs-deferred split the routing spine uses for the ensemble fold\nversus its execution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n"}, "03e7b042a735ead0a3568c10820db71bddd15ff0": {"t": 1785749579.8896687, "n": 15333, "h": "38d52a5893b75a82", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, DeepSeek, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, Groq, DeepSeek, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product sibling of the fleet account switcher (`fak fleet-accounts`,\nthe native Go successor to the legacy `tools/fleet_accounts.py` shim): provider-neutral,\ncredential-safe, and composable with the routing spine. It lives in\n`internal/modelroute/account.go` (pure, stdlib-only, the same package as the routing\ndecision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\nOpenAI-compatible providers such as Groq are represented as `kind: \"openai\"` plus their\nprovider `base_url`, so the wire adapter stays generic while the account id names whose\ncredential is being used.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" },\n { \"id\": \"july6netra_groq\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 1000,\n \"tokens_per_minute\": 8000, \"tokens_per_day\": 200000 },\n { \"id\": \"july6netra_groq_compound\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 250 },\n { \"id\": \"deepseek\", \"kind\": \"deepseek\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 },\n { \"id\": \"deepseek-anthropic\", \"kind\": \"anthropic\",\n \"base_url\": \"https://api.deepseek.com/anthropic\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" },\n { \"model\": \"qwen36-groq\", \"account\": \"july6netra_groq\", \"upstream_model\": \"qwen/qwen3.6-27b\" },\n { \"model\": \"groq-compound\", \"account\": \"july6netra_groq_compound\", \"upstream_model\": \"groq/compound\" },\n { \"model\": \"deepseek-pro\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-pro\" },\n { \"model\": \"deepseek-flash\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-flash\" },\n { \"model\": \"deepseek-chat-compat\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-chat\",\n \"compatibility_only\": true, \"deprecated_after_utc\": \"2026-07-24 15:59 UTC\",\n \"deprecated_alias_for\": \"deepseek-v4-flash non-thinking mode\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nClaude Code subscription seats add a second layer: **config-home accounts** (`CLAUDE_CONFIG_DIR`\nhomes such as `~/.claude-gem8-seat`) that may be logged in, logged out, tombstoned, disabled, or\nduplicated onto the same rate-limit bucket. That lifecycle is owned by `fak accounts`, not by the\nprovider roster.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## API-host probe bridge\n\nThe same model-account roster can feed the API-host no-spend probes:\n\n```bash\nfak api-host readiness --from-model-accounts examples/model-accounts.example.json\nfak api-host acceptance --from-model-accounts examples/model-accounts.example.json\n```\n\n`readiness` converts only OpenAI-compatible or local accounts into `/models` probe targets\n(`openai`, `openai-responses`, `xai`, `deepseek`, and `local`). Native provider accounts are not guessed into\nan OpenAI-shaped probe. `acceptance` keeps every account with a probeable base URL visible:\nOpenAI-compatible/local accounts can become `READY_FOR_LIVE_BRIDGE_RUN`, while native\nAnthropic/Gemini accounts are reported as `WIRE_SUPPORTED_UNPROBED`. Model hints come from the\nroster bindings, so the API-host report stays tied to the same abstract model ids the route policy\nuses.\n\nGroq Qwen3.6 uses the OpenAI-compatible base `https://api.groq.com/openai/v1`, credential\nenv var `FAK_GROQ_API_KEY`, and upstream model slug `qwen/qwen3.6-27b`. The example records\nthe current account/model limits as metadata: 30 requests/minute, 1,000 requests/day,\n8,000 tokens/minute, and 200,000 tokens/day. Groq Compound uses the same base and env var\nunder the separate account id `july6netra_groq_compound`, bound as the lower-quality\n`groq-compound` target to upstream slug `groq/compound`; its metadata is request-only:\n30 requests/minute and 250 requests/day, with no token-minute or token-day cap recorded.\nThe Claude dogfood launcher separately caps the outbound `max_tokens` field at 8192 for\n`groq/compound`, because Groq rejects larger per-response output budgets. Those values\nare advisory metadata for routing and launchers; the v1 resolver does not throttle\nrequests.\n\nDeepSeek V4 uses `deepseek-v4-pro` / `deepseek-v4-flash` on the OpenAI-compatible base\n`https://api.deepseek.com`; its Anthropic-compatible profile uses\n`https://api.deepseek.com/anthropic`. The legacy `deepseek-chat` and `deepseek-reasoner`\naliases are accepted only when marked `compatibility_only`, with the documented retirement\nmetadata `2026-07-24 15:59 UTC`, so a new roster does not silently bind to an alias that is\nabout to disappear.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## Managed-cache posture across launchers\n\nEvery fak launcher that fronts an agent with `fak guard` — `fak accounts launch`,\n`fak codex`, and the dispatch worker (`fak dispatch tick`/`auto`) — can hand guard a\n**managed-cache posture** that decides whether the gateway upgrades the stable-prefix\n`cache_control` breakpoint to the **1h-TTL** tier on the outbound Anthropic wire.\n\n**A subscription seat stays passive by default, on purpose.** Guard's own `--managed-cache\nauto` follows a never-speculate rule: it will not manage a wire whose billing it cannot see.\nA Pro/Max seat authenticates with subscription OAuth (`Authorization: Bearer`), whose cache\neconomics guard does not own, so `auto` resolves **passive** there and no launcher silently\nflips it. Nothing changes for a subscription fleet unless you ask for it.\n\n**An API-key-billed fleet reaches ACTIVE without hand-editing any launcher**, via two env\nknobs read once per launch and threaded onto the child's guard argv (not its environment, so\na *resumed* child — whose gateway env is deliberately stripped — keeps the posture):\n\n| Knob | Effect |\n|------|--------|\n| `FAK_GUARD_API_KEY_ENV=ANTHROPIC_API_KEY` | Names the env var holding the API key. On the Anthropic wire this is the explicit opt-IN to API billing that lets `auto` resolve **ACTIVE** (guard bills the key, not the seat's subscription). |\n| `FAK_MANAGED_CACHE=on\\|off\\|auto` | Forces the posture. `on` upgrades regardless of billing; `off` disables; `auto` (default) defers to the billing signal above. |\n\n`fak accounts launch --managed-cache ` and `fak codex --managed-cache ` expose\nthe same lever per-launch (defaulting to `$FAK_MANAGED_CACHE`); the dispatch worker reads the\nenv knobs only. An unknown mode fails loud at the front-ends; the headless worker warns and\nfalls back to `auto` so a fleet turn never dies over a cache-posture typo. Either way the\nguard child prints the RESOLVED posture in its startup banner, so the worker log witnesses\nwhether the 1h upgrade actually engaged. An unconfigured fleet emits no `--managed-cache`\nflag at all — the guard argv is byte-identical to before this knob existed.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch\n([#2528](https://github.com/anthony-chaudhary/fak/issues/2528)). v1 *resolves and prints*\na binding; it does not yet send a request over the wire to your chosen account. Building\nthe planners from the Targets and running an ensemble's members is the additive dispatch\nwiring, the same shipped-vs-deferred split the routing spine uses for the ensemble fold\nversus its execution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n"}, "46414b8bdfd96700ddd3288c7a556a24f9303634": {"t": 1785749581.1624277, "n": 13574, "h": "c9d4a86401ebf246", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, DeepSeek, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, DeepSeek, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product sibling of the fleet account switcher (`fak fleet-accounts`,\nthe native Go successor to the legacy `tools/fleet_accounts.py` shim): provider-neutral,\ncredential-safe, and composable with the routing spine. It lives in\n`internal/modelroute/account.go` (pure, stdlib-only, the same package as the routing\ndecision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" },\n { \"id\": \"deepseek\", \"kind\": \"deepseek\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 },\n { \"id\": \"deepseek-anthropic\", \"kind\": \"anthropic\",\n \"base_url\": \"https://api.deepseek.com/anthropic\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" },\n { \"model\": \"deepseek-pro\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-pro\" },\n { \"model\": \"deepseek-flash\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-flash\" },\n { \"model\": \"deepseek-chat-compat\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-chat\",\n \"compatibility_only\": true, \"deprecated_after_utc\": \"2026-07-24 15:59 UTC\",\n \"deprecated_alias_for\": \"deepseek-v4-flash non-thinking mode\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nClaude Code subscription seats add a second layer: **config-home accounts** (`CLAUDE_CONFIG_DIR`\nhomes such as `~/.claude-gem8-seat`) that may be logged in, logged out, tombstoned, disabled, or\nduplicated onto the same rate-limit bucket. That lifecycle is owned by `fak accounts`, not by the\nprovider roster.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## API-host probe bridge\n\nThe same model-account roster can feed the API-host no-spend probes:\n\n```bash\nfak api-host readiness --from-model-accounts examples/model-accounts.example.json\nfak api-host acceptance --from-model-accounts examples/model-accounts.example.json\n```\n\n`readiness` converts only OpenAI-compatible or local accounts into `/models` probe targets\n(`openai`, `openai-responses`, `xai`, `deepseek`, and `local`). Native provider accounts are not guessed into\nan OpenAI-shaped probe. `acceptance` keeps every account with a probeable base URL visible:\nOpenAI-compatible/local accounts can become `READY_FOR_LIVE_BRIDGE_RUN`, while native\nAnthropic/Gemini accounts are reported as `WIRE_SUPPORTED_UNPROBED`. Model hints come from the\nroster bindings, so the API-host report stays tied to the same abstract model ids the route policy\nuses.\n\nDeepSeek V4 uses `deepseek-v4-pro` / `deepseek-v4-flash` on the OpenAI-compatible base\n`https://api.deepseek.com`; its Anthropic-compatible profile uses\n`https://api.deepseek.com/anthropic`. The legacy `deepseek-chat` and `deepseek-reasoner`\naliases are accepted only when marked `compatibility_only`, with the documented retirement\nmetadata `2026-07-24 15:59 UTC`, so a new roster does not silently bind to an alias that is\nabout to disappear.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## Managed-cache posture across launchers\n\nEvery fak launcher that fronts an agent with `fak guard` — `fak accounts launch`,\n`fak codex`, and the dispatch worker (`fak dispatch tick`/`auto`) — can hand guard a\n**managed-cache posture** that decides whether the gateway upgrades the stable-prefix\n`cache_control` breakpoint to the **1h-TTL** tier on the outbound Anthropic wire.\n\n**A subscription seat stays passive by default, on purpose.** Guard's own `--managed-cache\nauto` follows a never-speculate rule: it will not manage a wire whose billing it cannot see.\nA Pro/Max seat authenticates with subscription OAuth (`Authorization: Bearer`), whose cache\neconomics guard does not own, so `auto` resolves **passive** there and no launcher silently\nflips it. Nothing changes for a subscription fleet unless you ask for it.\n\n**An API-key-billed fleet reaches ACTIVE without hand-editing any launcher**, via two env\nknobs read once per launch and threaded onto the child's guard argv (not its environment, so\na *resumed* child — whose gateway env is deliberately stripped — keeps the posture):\n\n| Knob | Effect |\n|------|--------|\n| `FAK_GUARD_API_KEY_ENV=ANTHROPIC_API_KEY` | Names the env var holding the API key. On the Anthropic wire this is the explicit opt-IN to API billing that lets `auto` resolve **ACTIVE** (guard bills the key, not the seat's subscription). |\n| `FAK_MANAGED_CACHE=on\\|off\\|auto` | Forces the posture. `on` upgrades regardless of billing; `off` disables; `auto` (default) defers to the billing signal above. |\n\n`fak accounts launch --managed-cache ` and `fak codex --managed-cache ` expose\nthe same lever per-launch (defaulting to `$FAK_MANAGED_CACHE`); the dispatch worker reads the\nenv knobs only. An unknown mode fails loud at the front-ends; the headless worker warns and\nfalls back to `auto` so a fleet turn never dies over a cache-posture typo. Either way the\nguard child prints the RESOLVED posture in its startup banner, so the worker log witnesses\nwhether the 1h upgrade actually engaged. An unconfigured fleet emits no `--managed-cache`\nflag at all — the guard argv is byte-identical to before this knob existed.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch\n([#2528](https://github.com/anthony-chaudhary/fak/issues/2528)). v1 *resolves and prints*\na binding; it does not yet send a request over the wire to your chosen account. Building\nthe planners from the Targets and running an ensemble's members is the additive dispatch\nwiring, the same shipped-vs-deferred split the routing spine uses for the ensemble fold\nversus its execution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n"}, "376b236051d27a4b3bd811c440c2d75157c141ff": {"t": 1785749582.3723514, "n": 12294, "h": "89f321d4d005c2cb", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product sibling of the fleet account switcher (`fak fleet-accounts`,\nthe native Go successor to the legacy `tools/fleet_accounts.py` shim): provider-neutral,\ncredential-safe, and composable with the routing spine. It lives in\n`internal/modelroute/account.go` (pure, stdlib-only, the same package as the routing\ndecision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nClaude Code subscription seats add a second layer: **config-home accounts** (`CLAUDE_CONFIG_DIR`\nhomes such as `~/.claude-gem8-seat`) that may be logged in, logged out, tombstoned, disabled, or\nduplicated onto the same rate-limit bucket. That lifecycle is owned by `fak accounts`, not by the\nprovider roster.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## API-host probe bridge\n\nThe same model-account roster can feed the API-host no-spend probes:\n\n```bash\nfak api-host readiness --from-model-accounts examples/model-accounts.example.json\nfak api-host acceptance --from-model-accounts examples/model-accounts.example.json\n```\n\n`readiness` converts only OpenAI-compatible or local accounts into `/models` probe targets\n(`openai`, `openai-responses`, `xai`, and `local`). Native provider accounts are not guessed into\nan OpenAI-shaped probe. `acceptance` keeps every account with a probeable base URL visible:\nOpenAI-compatible/local accounts can become `READY_FOR_LIVE_BRIDGE_RUN`, while native\nAnthropic/Gemini accounts are reported as `WIRE_SUPPORTED_UNPROBED`. Model hints come from the\nroster bindings, so the API-host report stays tied to the same abstract model ids the route policy\nuses.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## Managed-cache posture across launchers\n\nEvery fak launcher that fronts an agent with `fak guard` — `fak accounts launch`,\n`fak codex`, and the dispatch worker (`fak dispatch tick`/`auto`) — can hand guard a\n**managed-cache posture** that decides whether the gateway upgrades the stable-prefix\n`cache_control` breakpoint to the **1h-TTL** tier on the outbound Anthropic wire.\n\n**A subscription seat stays passive by default, on purpose.** Guard's own `--managed-cache\nauto` follows a never-speculate rule: it will not manage a wire whose billing it cannot see.\nA Pro/Max seat authenticates with subscription OAuth (`Authorization: Bearer`), whose cache\neconomics guard does not own, so `auto` resolves **passive** there and no launcher silently\nflips it. Nothing changes for a subscription fleet unless you ask for it.\n\n**An API-key-billed fleet reaches ACTIVE without hand-editing any launcher**, via two env\nknobs read once per launch and threaded onto the child's guard argv (not its environment, so\na *resumed* child — whose gateway env is deliberately stripped — keeps the posture):\n\n| Knob | Effect |\n|------|--------|\n| `FAK_GUARD_API_KEY_ENV=ANTHROPIC_API_KEY` | Names the env var holding the API key. On the Anthropic wire this is the explicit opt-IN to API billing that lets `auto` resolve **ACTIVE** (guard bills the key, not the seat's subscription). |\n| `FAK_MANAGED_CACHE=on\\|off\\|auto` | Forces the posture. `on` upgrades regardless of billing; `off` disables; `auto` (default) defers to the billing signal above. |\n\n`fak accounts launch --managed-cache ` and `fak codex --managed-cache ` expose\nthe same lever per-launch (defaulting to `$FAK_MANAGED_CACHE`); the dispatch worker reads the\nenv knobs only. An unknown mode fails loud at the front-ends; the headless worker warns and\nfalls back to `auto` so a fleet turn never dies over a cache-posture typo. Either way the\nguard child prints the RESOLVED posture in its startup banner, so the worker log witnesses\nwhether the 1h upgrade actually engaged. An unconfigured fleet emits no `--managed-cache`\nflag at all — the guard argv is byte-identical to before this knob existed.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch\n([#2528](https://github.com/anthony-chaudhary/fak/issues/2528)). v1 *resolves and prints*\na binding; it does not yet send a request over the wire to your chosen account. Building\nthe planners from the Targets and running an ensemble's members is the additive dispatch\nwiring, the same shipped-vs-deferred split the routing spine uses for the ensemble fold\nversus its execution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n"}, "17de060fd9dd94791652ac178df7bccdaa0e71d2": {"t": 1785749583.4441347, "n": 10135, "h": "08c67e698da0b6bd", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product sibling of the fleet account switcher (`fak fleet-accounts`,\nthe native Go successor to the legacy `tools/fleet_accounts.py` shim): provider-neutral,\ncredential-safe, and composable with the routing spine. It lives in\n`internal/modelroute/account.go` (pure, stdlib-only, the same package as the routing\ndecision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nClaude Code subscription seats add a second layer: **config-home accounts** (`CLAUDE_CONFIG_DIR`\nhomes such as `~/.claude-gem8-seat`) that may be logged in, logged out, tombstoned, disabled, or\nduplicated onto the same rate-limit bucket. That lifecycle is owned by `fak accounts`, not by the\nprovider roster.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## API-host probe bridge\n\nThe same model-account roster can feed the API-host no-spend probes:\n\n```bash\nfak api-host readiness --from-model-accounts examples/model-accounts.example.json\nfak api-host acceptance --from-model-accounts examples/model-accounts.example.json\n```\n\n`readiness` converts only OpenAI-compatible or local accounts into `/models` probe targets\n(`openai`, `openai-responses`, `xai`, and `local`). Native provider accounts are not guessed into\nan OpenAI-shaped probe. `acceptance` keeps every account with a probeable base URL visible:\nOpenAI-compatible/local accounts can become `READY_FOR_LIVE_BRIDGE_RUN`, while native\nAnthropic/Gemini accounts are reported as `WIRE_SUPPORTED_UNPROBED`. Model hints come from the\nroster bindings, so the API-host report stays tied to the same abstract model ids the route policy\nuses.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch\n([#2528](https://github.com/anthony-chaudhary/fak/issues/2528)). v1 *resolves and prints*\na binding; it does not yet send a request over the wire to your chosen account. Building\nthe planners from the Targets and running an ensemble's members is the additive dispatch\nwiring, the same shipped-vs-deferred split the routing spine uses for the ensemble fold\nversus its execution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n"}, "f67f54a54674fd4477072511ac188a446dd5645c": {"t": 1785749584.5847855, "n": 9228, "h": "f73cd000bd08b97f", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product sibling of the fleet account switcher (`fak fleet-accounts`,\nthe native Go successor to the legacy `tools/fleet_accounts.py` shim): provider-neutral,\ncredential-safe, and composable with the routing spine. It lives in\n`internal/modelroute/account.go` (pure, stdlib-only, the same package as the routing\ndecision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nClaude Code subscription seats add a second layer: **config-home accounts** (`CLAUDE_CONFIG_DIR`\nhomes such as `~/.claude-gem8-seat`) that may be logged in, logged out, tombstoned, disabled, or\nduplicated onto the same rate-limit bucket. That lifecycle is owned by `fak accounts`, not by the\nprovider roster.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch. v1 *resolves and prints* a binding; it does\nnot yet send a request over the wire to your chosen account. Building the planners from\nthe Targets and running an ensemble's members is the additive dispatch wiring, the same\nshipped-vs-deferred split the routing spine uses for the ensemble fold versus its\nexecution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n"}, "e675809573e8dbd17c4844145ec7fad63504090c": {"t": 1785749585.9280007, "n": 9170, "h": "a1bc12bc6761f5b1", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product form of the private fleet's account switcher\n(`tools/fleet_accounts.py`): provider-neutral, credential-safe, and composable with\nthe routing spine. It lives in `internal/modelroute/account.go` (pure, stdlib-only,\nthe same package as the routing decision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nClaude Code subscription seats add a second layer: **config-home accounts** (`CLAUDE_CONFIG_DIR`\nhomes such as `~/.claude-gem8-seat`) that may be logged in, logged out, tombstoned, disabled, or\nduplicated onto the same rate-limit bucket. That lifecycle is owned by `fak accounts`, not by the\nprovider roster.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch. v1 *resolves and prints* a binding; it does\nnot yet send a request over the wire to your chosen account. Building the planners from\nthe Targets and running an ensemble's members is the additive dispatch wiring, the same\nshipped-vs-deferred split the routing spine uses for the ensemble fold versus its\nexecution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n"}, "6fe5b38506ed9da5e0f748986e3affbc53d780af": {"t": 1785749587.1613963, "n": 8862, "h": "1facd36158a493f1", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product form of the private fleet's account switcher\n(`tools/fleet_accounts.py`): provider-neutral, credential-safe, and composable with\nthe routing spine. It lives in `internal/modelroute/account.go` (pure, stdlib-only,\nthe same package as the routing decision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nClaude Code subscription seats add a second layer: **config-home accounts** (`CLAUDE_CONFIG_DIR`\nhomes such as `~/.claude-gem8-seat`) that may be logged in, logged out, tombstoned, disabled, or\nduplicated onto the same rate-limit bucket. That lifecycle is owned by `fak accounts`, not by the\nprovider roster.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch. v1 *resolves and prints* a binding; it does\nnot yet send a request over the wire to your chosen account. Building the planners from\nthe Targets and running an ensemble's members is the additive dispatch wiring, the same\nshipped-vs-deferred split the routing spine uses for the ensemble fold versus its\nexecution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n"}, "38679b80e87ef382339b9fcfcf957dde67a04506": {"t": 1785749588.3718538, "n": 7335, "h": "cd17dc0f05c603d3", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product form of the private fleet's account switcher\n(`tools/fleet_accounts.py`): provider-neutral, credential-safe, and composable with\nthe routing spine. It lives in `internal/modelroute/account.go` (pure, stdlib-only,\nthe same package as the routing decision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch. v1 *resolves and prints* a binding; it does\nnot yet send a request over the wire to your chosen account. Building the planners from\nthe Targets and running an ensemble's members is the additive dispatch wiring, the same\nshipped-vs-deferred split the routing spine uses for the ensemble fold versus its\nexecution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n"}, "6875bebcb530699bbaaec2ad2b067f33114a4483": {"t": 1785749589.4754295, "n": 7042, "h": "1be3868b2d422e91", "c": "# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product form of the private fleet's account switcher\n(`tools/fleet_accounts.py`): provider-neutral, credential-safe, and composable with\nthe routing spine. It lives in `internal/modelroute/account.go` (pure, stdlib-only,\nthe same package as the routing decision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch. v1 *resolves and prints* a binding; it does\nnot yet send a request over the wire to your chosen account. Building the planners from\nthe Targets and running an ensemble's members is the additive dispatch wiring, the same\nshipped-vs-deferred split the routing spine uses for the ensemble fold versus its\nexecution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n"}, "13f5f1a6e0391eb28bcbea759890fb009251e5d1": {"t": 1786067822.341199, "n": 43276, "h": "6167b7aaac2b701b", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, DeepSeek, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, Groq, DeepSeek, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product sibling of the fleet account switcher (`fak fleet-accounts`,\nthe native Go successor to the legacy `tools/fleet_accounts.py` shim): provider-neutral,\ncredential-safe, and composable with the routing spine. It lives in\n`internal/modelroute/account.go` (pure, stdlib-only, the same package as the routing\ndecision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\nOpenAI-compatible providers such as Groq are represented as `kind: \"openai\"` plus their\nprovider `base_url`, so the wire adapter stays generic while the account id names whose\ncredential is being used.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" },\n { \"id\": \"july6netra_groq\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 1000,\n \"tokens_per_minute\": 8000, \"tokens_per_day\": 200000 },\n { \"id\": \"july6netra_groq_compound\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 250 },\n { \"id\": \"deepseek\", \"kind\": \"deepseek\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 },\n { \"id\": \"deepseek-anthropic\", \"kind\": \"anthropic\",\n \"base_url\": \"https://api.deepseek.com/anthropic\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" },\n { \"model\": \"qwen36-groq\", \"account\": \"july6netra_groq\", \"upstream_model\": \"qwen/qwen3.6-27b\" },\n { \"model\": \"groq-compound\", \"account\": \"july6netra_groq_compound\", \"upstream_model\": \"groq/compound\" },\n { \"model\": \"deepseek-pro\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-pro\" },\n { \"model\": \"deepseek-flash\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-flash\" },\n { \"model\": \"deepseek-chat-compat\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-chat\",\n \"compatibility_only\": true, \"deprecated_after_utc\": \"2026-07-24 15:59 UTC\",\n \"deprecated_alias_for\": \"deepseek-v4-flash non-thinking mode\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nCoding-agent subscriptions add a second layer: **config-home accounts**. Claude uses\n`CLAUDE_CONFIG_DIR` homes such as `~/.claude-gem8-seat`; Codex uses `CODEX_HOME` homes such as\n`~/.codex` or `~/.codex-work`. `fak fleet-accounts` discovers both, derives their non-secret\nprovider-account identity, collapses duplicate homes on the same rate-limit bucket, and offers\nonly homes whose credential state is ready. The default Codex picker profile is\n`gpt-5.6-sol` with `model_reasoning_effort=xhigh`.\n\nThe authored lifecycle registry behind `fak accounts` remains Claude-specific today; its seat\nnames and tombstones are therefore applied only to Claude rows. Codex rows take lifecycle/login\ntruth from their own home (`auth.json`/`config.toml`) and never inherit a same-named Claude seat\nsuch as `default`.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## API-host probe bridge\n\nThe same model-account roster can feed the API-host no-spend probes:\n\n```bash\nfak api-host readiness --from-model-accounts examples/model-accounts.example.json\nfak api-host acceptance --from-model-accounts examples/model-accounts.example.json\n```\n\n`readiness` converts only OpenAI-compatible or local accounts into `/models` probe targets\n(`openai`, `openai-responses`, `xai`, `deepseek`, and `local`). Native provider accounts are not guessed into\nan OpenAI-shaped probe. `acceptance` keeps every account with a probeable base URL visible:\nOpenAI-compatible/local accounts can become `READY_FOR_LIVE_BRIDGE_RUN`, while native\nAnthropic/Gemini accounts are reported as `WIRE_SUPPORTED_UNPROBED`. Model hints come from the\nroster bindings, so the API-host report stays tied to the same abstract model ids the route policy\nuses.\n\nGroq Qwen3.6 uses the OpenAI-compatible base `https://api.groq.com/openai/v1`, credential\nenv var `FAK_GROQ_API_KEY`, and upstream model slug `qwen/qwen3.6-27b`. The example records\nthe current account/model limits as metadata: 30 requests/minute, 1,000 requests/day,\n8,000 tokens/minute, and 200,000 tokens/day. Groq Compound uses the same base and env var\nunder the separate account id `july6netra_groq_compound`, bound as the lower-quality\n`groq-compound` target to upstream slug `groq/compound`; its metadata is request-only:\n30 requests/minute and 250 requests/day, with no token-minute or token-day cap recorded.\nThe Claude dogfood launcher separately caps the outbound `max_tokens` field at 8192 for\n`groq/compound`, because Groq rejects larger per-response output budgets. Those values\nare advisory metadata for routing and launchers; the v1 resolver does not throttle\nrequests.\n\nDeepSeek V4 uses `deepseek-v4-pro` / `deepseek-v4-flash` on the OpenAI-compatible base\n`https://api.deepseek.com`; its Anthropic-compatible profile uses\n`https://api.deepseek.com/anthropic`. The legacy `deepseek-chat` and `deepseek-reasoner`\naliases are accepted only when marked `compatibility_only`, with the documented retirement\nmetadata `2026-07-24 15:59 UTC`, so a new roster does not silently bind to an alias that is\nabout to disappear.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open. The same file pins the tier-1 mirror\n(`modelroute.IsRemoteRoute`) against the enforcing floor over one corpus, because\n`internal/modelroute` sits below `internal/engine` and must keep its own copy of the\non-box family list. See *Placement zones* below for the third rung the binary\nlocal/remote split does not name.\n\n## Placement zones: your box, your company's boxes, someone else's\n\nLocality is not binary. A company that self-hosts runs models in **three** places, and\nan account declares which by its `kind`:\n\n| Zone | `kind` | Where the weights sit | Self-hosted? | On-box? |\n| --- | --- | --- | --- | --- |\n| `device` | `local` | the engineer's own machine (loopback only) | yes | **yes** |\n| `fleet` | `fleet` | a machine the **organization** operates | yes | no |\n| `vendor` | `openai` / `anthropic` / `gemini` / `xai` / `deepseek` / `openai-responses` | a third-party lab's API | no | no |\n\nThe middle rung is the one that carries the token volume in a self-hosting shop — a\nGLM- or Kimi-class open model on company GPUs, shared by every engineer. Before\n`kind: fleet` existed it could not be written down: as `local` it was refused (a local\naccount must carry a loopback base URL), and as `openai` it was admitted but became\nindistinguishable from `api.openai.com` in every downstream record, so the org got no\ncredit for hardware it owns.\n\n```json\n{\n \"id\": \"corp-glm\",\n \"kind\": \"fleet\",\n \"base_url\": \"http://glm.infer.corp.internal:8000/v1\",\n \"label\": \"GLM-5.2 vLLM server the company operates\"\n}\n```\n\nA `fleet` account needs an explicit, non-loopback `http(s)` endpoint (there is no public\ndefault for a host only your org can name, and a loopback address is `device` by\ndefinition — the zones must stay disjoint). Unlike a vendor account it does **not**\nrequire a `cred_env`: an org-operated server on a private network commonly has no API\nkey. Supply one when the endpoint authenticates.\n\n**Self-hosted is not the same as on-box, deliberately.** `ZoneFleet.SelfHosted()` is\ntrue — its tokens count toward \"we ran this on our own silicon\" — but\n`ZoneFleet.OnBox()` is false, so the residency floor still treats a `fleet:…` route as\nremote and still denies a tenant-scoped payload routed there. Naming the zone changes\nwhat fak can *attribute*, not what it *permits*. Letting an org-operated host carry a\nsensitive payload is an enforcement change that needs an operator-declared trust\nboundary (authenticated transport, a named host allowlist), and it is tracked\nseparately — it does not arrive as a side effect of declaring a zone.\n\nThe zones are ordered as an escalation ladder — `device` (0) → `fleet` (1) →\n`vendor` (2) — so \"prefer the cheapest rung that can do the work\" is a comparison on a\ndeclared value rather than a string switch. An unrecognized zone ranks *above* vendor\nand is neither self-hosted nor on-box: unattributable never reads as free or as safe.\n\n### Walking the ladder: `Roster.Place`\n\n`Roster.Place(class, candidates)` is what actually walks it. Given a `WorkClass` and the\nmodels you have bound — each with the work tier you have evidence it can serve — it\nreturns the cheapest rung that can take the job, plus the full ladder walk that produced\nthe answer:\n\n```go\np, err := roster.Place(modelroute.ClassRoutine, []modelroute.Candidate{\n {Model: \"tiny\", Capability: modelroute.TierT2, Measured: true},\n {Model: \"corp-mid\", Capability: modelroute.TierT1, Measured: true},\n {Model: \"frontier\", Capability: modelroute.TierT0, Measured: true},\n})\n// p.Zone == ZoneDevice, p.SelfHosted() == true, p.Escalated == false\n```\n\nTwo rules stop it from being wishful thinking:\n\n**The floor belongs to the work, not to the model.** Admission runs through\n`TierPolicy.Admit`, so a cheap rung cannot take work above its tier however available it\nis. Security/release/destructive work has a floor that never drops to routine, so it\nskips a measured local model and lands on the fleet — `p.Escalated` is true and the\ndevice rung reports `zone-under-tier`. An unrecognized class stays at the strictest floor\nrather than inferring a cheap one.\n\n**Unmeasured capability may not descend the ladder.** `Candidate.Measured` records\nwhether `Capability` is a measurement or an assertion, and an assertion is skipped on\nevery rung below the vendor. This matters right now: `internal/ablate`'s `StubTierScorer`\nmeasures nothing, so a placer that trusted asserted grades would route everything to the\ncheapest rung on a number nobody computed, and would look like it was working. Until\ncapability measurement lands, an operator opts a rung in by marking a candidate\n`Measured` — a claim they can be held to. `TestUnmeasuredCapabilityCannotDescendTheLadder`\npins both halves: an all-asserted ladder places on the vendor, and the same work moves to\nthe device the moment the cheap rung is measured.\n\nEvery rung is reported, including the ones passed over, in a closed reason vocabulary\n(`placed-in-zone`, `zone-under-tier`, `zone-capability-unmeasured`, `zone-no-candidate`,\n`zone-not-reached`, `escalated-past-cheaper-zone`) — because \"why is this *still* going to\na vendor?\" is the question an operator actually asks. A candidate the roster cannot\nresolve is a loud error, never a silent skip: a typo in a placement config must surface as\na misconfiguration rather than as traffic quietly continuing to bill a vendor.\n\nThe trap this code is written around is that `WorkTier` numbers are **inverted** — T0 is\nthe most demanding but the lowest number. Every comparison goes through\n`WorkTier.MeetsRequirement`, never a raw `<` or `>=`. Substituting the natural-looking\n`capability >= required` fails four tests at once, and the way it fails is the point: a\n4B laptop model takes both the ultra-hard and the security/destructive work.\n\n### Declaring what the work is\n\n`Place` needs a `WorkClass`; routing produces a `Subject`. `ClassOf(Subject)` is the\njoint, and it is where an automatic placer is most tempted to cheat — it would be easy to\nlook at a tool name, decide \"grep is harmless\", and route it to a laptop. fak does not do\nthat. Judging what a call can destroy is `internal/adjudicator`'s reversibility question,\nit sits a tier above this leaf, and it is deliberately not re-guessed from a name here.\n\nSo a work class is **declared, or it is conservative**. Declare it as a subject label:\n\n```jsonc\n{ \"match\": { \"aspect\": \"tool_call\", \"labels\": { \"work_class\": \"routine\" } },\n \"plan\": { \"model\": \"tiny\" } }\n```\n\n| Input | Classified as | Why |\n| --- | --- | --- |\n| `labels.work_class` naming a known class | that class, **declared** | the operator said so |\n| `aspect: \"scout\"`, nothing declared | `routine`, **declared** | a scout *is* the cheap classify-first probe — the aspect names the work |\n| anything else | *(empty)*, **undeclared** | `PolicyFor` already puts the empty class at the strictest floor |\n| `labels.work_class` misspelled | *(empty)*, **undeclared** | a typo in `routine` must not read as permission to use a laptop |\n\nUndeclared work therefore lands exactly where it landed before this feature existed — on\na vendor — and reports `class-undeclared-conservative` so an operator can see the fix is\na label rather than more hardware. There is no second conservatism mechanism layered on\ntop; one gate in one place is why the behaviour stays predictable. `Complexity` can then\nratchet a floor **up** (a high-complexity `routine` subject becomes `normal-impl`) and\nnever down, which is what makes accepting complexity as an input safe at all.\n\n`Roster.PlaceSubject(subject, candidates)` composes the two and returns the placement\n*and* the classification, so both halves of the decision are visible together.\n\n### Declaring what a sub-agent type does\n\nSub-agents are where the cheap rungs would pay for themselves — a fleet spawns far more\ndelegated work than top-level turns — and `Roster.PlaceSpawn(parent, class, candidates)`\nalready answers \"which rung should this child run on\". It refuses an **empty** work class\non purpose: an unclassified spawn is a missing classification, not a routine one, and\nletting delegation reach a laptop without stating what the work *is* would be exactly the\nshape a floor bypass takes.\n\nThat refusal is what left the call unreachable. A spawn arrives as an admitted tool call\ncarrying an agent **type** and a prompt. The prompt is prose, and reading a class out of\nprose is the guess `ClassOf` refuses everywhere else. The type is structured — but it is\nnot a work class: only an operator knows whether the agent type *their* fleet calls\n`explore` does bounded lookup or ships code.\n\nSo it is declared, in the roster the operator already owns:\n\n```jsonc\n\"spawn_classes\": [\n { \"type\": \"explore\", \"class\": \"routine\" },\n { \"type\": \"code-reviewer\", \"class\": \"normal-impl\" },\n { \"type\": \"release-cutter\", \"class\": \"security-release-destructive\" }\n]\n```\n\n`Roster.SpawnClassFor(type)` resolves one, returning `(class, declared bool)`. The rules\nare the ones the rest of this leaf runs on:\n\n| Input | Resolves to | Why |\n| --- | --- | --- |\n| a declared type (case- and space-insensitive) | that class, **declared** | the operator said so |\n| a type nobody declared, or no `spawn_classes` block at all | *(empty)*, **undeclared** | `PlaceSpawn` then refuses — the spawn keeps whatever placement it has today |\n| a declared type whose class token this package does not know | *(empty)*, **undeclared** | same rule as a misspelled `work_class` label: a typo is not permission to use a laptop |\n\nThe match is **exact**, never a prefix and never a glob — `explore` must not answer for\n`explore-and-delete`. A malformed entry is refused at load rather than skipped: an empty\ntype, an empty or unknown class (the error names the four options), a duplicate type, or a\ntype carrying a route delimiter. A silently-dropped entry is the worst outcome available,\nbecause it looks correct from the operator's side while behaving like an undeclared one,\nand nothing points at the typo.\n\n**Shipped:** the declaration, its validation, and the resolver — plus the block in\n`examples/model-accounts.example.json`, which the test suite parses and resolves.\n**Not shipped:** nothing on the spawn path calls `SpawnClassFor` yet. The gateway does\nobserve spawns (`internal/gateway/adjudicate_proposed.go` stamps every admitted tool call\nand already recognises the spawn-shaped ones — that is where `spawn_count` comes from),\nand that is the seam a placement call would bind to, but binding it changes where\ndelegated traffic runs and is a separate, individually reviewable change under\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416). Declaring\n`spawn_classes` today moves no traffic.\n\nOne neighbouring field is *not* this signal, and it has already been mistaken for it: a\nsession's `ParentTrace`/`Generation` is **continuation** lineage — the same agent after a\nbudget-reset re-continuation — written only by `session.Table.Recontinue`. It never marks\na row as somebody's child. `SpawnCount` is the sub-agent axis, and it is parent-side: it\ncounts children a trace spawned, never whose child a trace is.\n\n### Seeing the ladder before it moves traffic: `fak route --place`\n\n```console\n$ fak route --accounts accounts.json --place --labels work_class=routine\n```\n\nThe oracle walks the ladder for one subject against your real roster and prints the rung\nit chose, every rung it passed over, and the closed reason token for each. It exists\n*before* anything on the dispatch path calls `Place`, deliberately: a placement policy\nnobody can inspect is a policy nobody can be held to, and an operator has to be able to\nsee what the ladder would do with **their** roster before fak starts moving traffic on\nthe strength of it. Add `--json` for the same answer as a report.\n\nThe candidate pool is every model the roster **binds**, not just the one routing picked —\nthe question is which rung can serve this class of work at all. Compatibility-only and\ndeprecated-alias bindings are excluded, since admitting a legacy spelling would let the\nsame hardware appear twice on a rung.\n\nAdd `--spawn-type TYPE` to ask the *delegated* question in the same breath: where would a\nsub-agent of that type run, given that this turn ran where the block above says. Against\nthe shipped example roster:\n\n```console\n$ fak route --accounts examples/model-accounts.example.json --place \\\n --labels work_class=ultra-hard --spawn-type explore \\\n --capability zone-device=t2,zone-fleet=t1,large=t0\n```\n```\nSPAWN PLACEMENT (--spawn-type explore)\n work class routine [declared by the roster's spawn_classes]\n parent zone=vendor model=deepseek-flash\n placed zone=device model=zone-device\n self-hosted yes escalated no failed-over no\n relation spawn-descended-from-parent-zone spawn-inherit-unmeasured\n descent yes self-hosted descent yes\n```\n\nThe child's class comes from the `spawn_classes` declaration above — never from the\nparent, the prompt, or the spelling of the type — so an **undeclared** type is refused\nwith the roster's own list of declared ones rather than assumed routine. The parent's rung\nis *recorded, not obeyed*: a sub-agent spawned from a frontier turn still lands on the\nlaptop, which is the whole point, and `self-hosted descent` is the event\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416) counts. The report also\nanswers the counterfactual — what *inheriting* the parent's model would have done — and\nsays `UNKNOWN` when the parent was never graded, because \"the status quo was fine here\"\nis a claim that needs a measurement behind it. `--json` carries the same answer under a\n`spawn` key that is **absent** when the question was not asked.\n\n### Saying what a model can do\n\nBecause unmeasured capability may not descend the ladder, a fresh roster places\n*everything* on the vendor rung — correctly, and uselessly. There are three ways to give\nthe ladder something to descend on, in increasing order of what they cost to produce:\n\n| Flag | What it is | What it costs to be wrong |\n| --- | --- | --- |\n| `--capability qwen3.6-4b=t2,glm-5.2=t1` | an operator **asserting** a grade | attributable to a person, but does not scale past a handful of models |\n| `--evidence FILE` | a summary of observed outcomes, graded here | someone assembled the counts; you are trusting their arithmetic |\n| `--outcomes FILE` | the append-only turn journal, **counted here** | nothing is trusted but the record itself |\n\nAll three feed one grader (`modelroute.GradeCapability`), and its bar is yours:\n\n```console\n$ fak route --accounts accounts.json --place --labels work_class=routine \\\n --outcomes turns.jsonl --since 30d --grade-floor attempts=50,rate=0.9,witness\n```\n\nThe default bar is 20 independently verified attempts at 80%. There is deliberately **no\nknob that makes self-report count** — a model's own claim of 500/500 successes buys\nnothing, and the refused attempts are reported *as refused* rather than as absent. An\noperator who wants to assert a capability can already do so, attributably, with\n`--capability`. A model claimed by both an assertion and a measurement is a named\nrefusal: they cannot both be the grade.\n\nA grade is the **floor** of the work observed, never its optimal tier, and a model that\nfails the bar is `UNMEASURED` **with a reason** rather than graded at the worst tier. The\nthree reasons call for opposite responses and are never collapsed into one:\n\n| Reason | The fix |\n| --- | --- |\n| `no-trusted-evidence` | run the turns under a witness or a judge — the volume is there, the provenance is not |\n| `insufficient-samples` | run more turns, or lower `attempts=` if you meant to |\n| `below-success-floor` | this model cannot do this work; that is the answer |\n\n### Grading from the turn journal\n\n`--outcomes` reads the record itself — one JSON object per line, appended as turns\nhappen (`modelroute.TurnOutcome`):\n\n```jsonl\n{\"id\":\"t-4471\",\"model\":\"qwen3.6-4b\",\"class\":\"routine\",\"zone\":\"device\",\"success\":true,\"verify\":\"witness\",\"at\":\"2026-07-24T11:02:00Z\"}\n```\n\nThe fold is written to **lose** evidence rather than invent it, because each alternative\ninflates a claim, and it reports every loss:\n\n```\n journal turns.jsonl: 1463 line(s), 1332 counted, 4 model(s) with evidence\n not counted 1 unparseable line(s), 30 replayed id(s), 60 older than 30d, 40 undated\n```\n\n- A repeated `id` is counted **once**. Replaying a file is the cheapest possible way to\n manufacture a grade: the same 20 successes appended three times must not read as 60.\n- An **undated** row cannot be shown to be inside a window, so `--since` excludes it — and\n counts it apart from a stale one, because one needs a wider window and the other needs a\n producer that stamps its rows. Ask for no window and a missing date costs nothing.\n- **Provenance is never merged.** 100 self-reported turns and 20 witnessed ones about the\n same model stay two rows; merging them would force a pick of provenance, and any pick\n is a lie. The grade keeps the weakest provenance behind whatever it merged.\n- A **torn line** is skipped and counted, never fatal. A fleet appending to this file will\n eventually die mid-write, and discarding thousands of verified turns over one partial\n row would make the honest path the fragile one.\n- Rows with no `id` are kept — a missing id is not proof of a duplicate — but reported as\n a corpus that **cannot be checked for replay**, since that is a fact about how much the\n number is worth.\n\n`--evidence` and `--outcomes` are mutually exclusive: a run that took some of its answer\nfrom a journal and some from a hand-written summary produces a grade whose provenance\nnobody can state afterwards.\n\n**What is not shipped:** nothing yet *writes* this journal. `internal/ablate`'s\n`StubTierScorer` still grades nothing, so today the file is something you produce — from\nDOS's git witness, a judge score, or your own harness. That producer is the one piece\nstanding between this ladder and traffic that moves by itself.\n\n### Placing against what is actually answering\n\nA grade says what a model *can* do. It says nothing about whether the box is powered on.\n`--serving FILE` hands the ladder a liveness snapshot — what your own probes just saw —\nand the ladder walks past candidates it reports are not serving:\n\n```json\n{\n \"schema\": \"fak.modelroute.serving.v1\",\n \"as_of_unix\": 1785312000,\n \"max_age_seconds\": 120,\n \"covers\": [\"device\", \"fleet\"],\n \"models\": {\n \"zone-device\": {\"state\": \"up\", \"observed_unix\": 1785311950},\n \"zone-fleet\": {\"state\": \"down\", \"observed_unix\": 1785311950},\n \"zone-fleet-agentic\": {\"state\": \"up\", \"observed_unix\": 1785311950}\n }\n}\n```\n\n```console\n$ fak route --accounts examples/model-accounts.example.json --place \\\n --labels work_class=normal-impl --serving serving.json \\\n --capability zone-device=t2,zone-fleet=t1,zone-fleet-agentic=t1,large=t0\n```\n```\n placed zone=fleet model=zone-fleet-agentic\n self-hosted yes escalated yes failed-over yes\n ladder\n device - zone-capability-unmeasured x2 zone-under-tier\n fleet zone-fleet-agentic zone-serving-down placed-in-zone escalated-past-cheaper-zone\n vendor - zone-not-reached\n\nSERVING SNAPSHOT (--serving serving.json)\n as of 1785312000 max age 120s\n covers device fleet\n observations 3, of which 3 name a model this roster binds\n SILENT small tiny-classifier\n the snapshot claims to speak for these candidates' rungs and then says\n nothing about them, so they are passed over as unknown rather than\n assumed well. That is a gap in the probe, not an outage.\n```\n\n**One dead GPU host is not a dead rung.** A fleet is several machines, and abandoning the\nrung because one box rebooted sends every token to a third-party lab to route around a\nneighbour that was idle — so the failover above stayed on the company's own hardware.\n\n| Verdict | What it means | Placed? |\n| --- | --- | --- |\n| `zone-serving-down` | observed unable to serve | passed over |\n| `zone-serving-unknown` | no verdict, on a rung the snapshot *claims* to cover | passed over |\n| `zone-serving-stale` | a verdict that cannot be shown fresh | passed over |\n| `zone-serving-degraded` | serving under strain | **recorded, and placed anyway** |\n\nDegraded is deliberately not actionable: a loaded host still takes work, and shedding at\nthe first sign of queueing inverts self-hosting exactly in the busy hour it is meant for.\n\nThree rules decide the rest, and each is fail-closed in a different direction:\n\n- **Silence only means something where you said it would.** `covers` is the operator\n declaring which rungs the probe actually watches; a candidate the snapshot is silent\n about *inside* that coverage is unknown, not healthy, because a crashed prober must not\n read as a fleet in perfect health. Outside coverage, silence gates nothing.\n- **An observation is always honored**, coverage or not. Coverage governs what silence\n means, never what a verdict means — a report that says a host is down is not overruled\n by a `covers` list that forgot to mention its rung.\n- **Freshness is fail-closed, and so is every way of not having it**: no observation\n stamp, no `as_of_unix`, past `max_age_seconds`, or stamped *after* the report all read\n as stale. A snapshot with a bound and no clock to measure against therefore gates\n everything it covers, which is correct and reads exactly like a total outage — so the\n summary says `STALE-ALL` and names the missing field rather than leaving you to guess.\n\n**`failed-over` is carried apart from `escalated`** because they want opposite fixes.\nEscalated says a cheaper rung could not do this work — a capability fact, stable until\nsomeone changes the roster. Failed-over says a rung that *could* have was routed around\nbecause something was not answering — an operations fact that may already be untrue. A\nbill read without that second bit shows vendor spend attributed to work that \"needed\" a\nvendor, and sends an operator hunting a capability problem that does not exist while the\ndead host stays dead. Both are printed for every placement, including the ones that stayed\ncheap: a failover *within* a rung cost nothing and still means something is down.\n\nThe snapshot and the roster are validated apart and meet only here, so the report also\nnames what neither half can see alone:\n\n```\n observations 2, of which 0 name a model this roster binds\n UNBOUND glm-5.2 kimi-k3\n these observations gate NOTHING: the ladder only ever asks about\n models the roster binds, so a probe filed under an id nothing binds\n is honored nowhere and this run is identical to one with no snapshot.\n Check the ids against the roster's bindings, not its upstream names.\n Each of these is the UPSTREAM name of a candidate: the probe filed its\n result under the name it dialled, and the ladder asks by routed id.\n glm-5.2 -> zone-fleet\n kimi-k3 -> zone-fleet-agentic\n```\n\nThat is the fail-open the flag would otherwise ship: a clean-validating report, an\noperator who believes they gated a dead host, and a placement identical to one with no\nsnapshot at all. The same typo *inside* declared coverage fails the other way instead —\nthe real candidate is now silent on a rung the report speaks for, so it is passed over as\nunknown and a healthy host looks dead. One id appearing in `UNBOUND` and its routed name\nin `SILENT` is the signature of exactly this mistake. Where an upstream name resolves to\n**two** candidates, both are named: two accounts serving the same weights is how a rung\nscales, and one observation cannot speak for both pools — which is why the snapshot is\nkeyed by routed id in the first place.\n\nTwo smaller refusals hold the same line. An unknown field is rejected rather than ignored,\nbecause `max_age_sec` otherwise parses, validates, looks right, and silently switches\nfail-closed freshness *off*. And a named file that cannot be read is an error, never an\nempty report: dropping the flag means \"no liveness signal\", while a missing file means\nyour probe did not run, and placing work as though every host were healthy is precisely\nthe failure the gate exists to prevent. In `--json` the `serving` key is **absent** unless\na snapshot was supplied, so it can never be misread as a probe that saw nothing.\n\n**What is not shipped:** nothing produces this file either. It is deliberately not a probe\nof fak's own — placement is asserted to be pure (same roster, same candidates, same\nreport, same answer), and a CLI that dialled endpoints would be reading a clock and a\nnetwork inside a decision that reads neither. The snapshot is your monitoring's output,\nin fak's vocabulary.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## Managed-cache posture across launchers\n\nEvery fak launcher that fronts an agent with `fak guard` — `fak accounts launch`,\n`fak codex`, and the dispatch worker (`fak dispatch tick`/`auto`) — can hand guard a\n**managed-cache posture** that decides whether the gateway upgrades the stable-prefix\n`cache_control` breakpoint to the **1h-TTL** tier on the outbound Anthropic wire.\n\n**A subscription seat stays passive by default, on purpose.** Guard's own `--managed-cache\nauto` follows a never-speculate rule: it will not manage a wire whose billing it cannot see.\nA Pro/Max seat authenticates with subscription OAuth (`Authorization: Bearer`), whose cache\neconomics guard does not own, so `auto` resolves **passive** there and no launcher silently\nflips it. Nothing changes for a subscription fleet unless you ask for it.\n\n**An API-key-billed fleet reaches ACTIVE without hand-editing any launcher**, via two env\nknobs read once per launch and threaded onto the child's guard argv (not its environment, so\na *resumed* child — whose gateway env is deliberately stripped — keeps the posture):\n\n| Knob | Effect |\n|------|--------|\n| `FAK_GUARD_API_KEY_ENV=ANTHROPIC_API_KEY` | Names the env var holding the API key. On the Anthropic wire this is the explicit opt-IN to API billing that lets `auto` resolve **ACTIVE** (guard bills the key, not the seat's subscription). |\n| `FAK_MANAGED_CACHE=on\\|off\\|auto` | Forces the posture. `on` upgrades regardless of billing; `off` disables; `auto` (default) defers to the billing signal above. |\n\n`fak accounts launch --managed-cache ` and `fak codex --managed-cache ` expose\nthe same lever per-launch (defaulting to `$FAK_MANAGED_CACHE`); the dispatch worker reads the\nenv knobs only. An unknown mode fails loud at the front-ends; the headless worker warns and\nfalls back to `auto` so a fleet turn never dies over a cache-posture typo. Either way the\nguard child prints the RESOLVED posture in its startup banner, so the worker log witnesses\nwhether the 1h upgrade actually engaged. An unconfigured fleet emits no `--managed-cache`\nflag at all — the guard argv is byte-identical to before this knob existed.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch\n([#2528](https://github.com/anthony-chaudhary/fak/issues/2528)). v1 *resolves and prints*\na binding; it does not yet send a request over the wire to your chosen account. Building\nthe planners from the Targets and running an ensemble's members is the additive dispatch\nwiring, the same shipped-vs-deferred split the routing spine uses for the ensemble fold\nversus its execution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n\n**Placement zones — shipped and deferred, precisely.** *Shipped:* the vocabulary. A\ncompany-operated server is expressible (`kind: fleet`), its account validates on its own\nterms, its route string round-trips to `ZoneFleet`, and `PlacementZone.SelfHosted()`\ngives every downstream consumer one predicate to attribute a token by. Witnessed by\n`TestZonesPartitionTheKnownKinds`, `TestEngineRouteCarriesTheZoneBackOut`,\n`TestShippedExampleRosterCarriesAllThreeZones`, and — at the enforcing floor —\n`TestFleetZoneStaysRemoteAtTheFloor` plus `TestTierOneRouteMirrorAgreesWithTheFloor`.\n*Also shipped:* `Roster.Place` **chooses** a zone (see *Walking the ladder* above).\n*Deferred, and deliberately so:* nothing yet calls it on the live dispatch path, the\nserving path does not yet **record** the zone it used, and the residency floor does not\nyet treat a fleet host as inside a trust boundary. Zone-aware placement, the self-hosted\ntoken-fraction metric, and the escalation ladder are separate, individually reviewable\nchanges on top of this vocabulary — not implied by it. They are tracked as\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416).\n\n### The first consumer: honest stratum attribution\n\n`internal/sessionaudit` classifies audited turns into billing buckets. It used to answer\n\"was this self-hosted?\" from the **model id**, which cannot support the claim: Qwen,\nLlama, Mistral, DeepSeek, GLM and Kimi all publish open weights, so each is servable\neither from hardware you operate or from a vendor's API. That produced two contradictions\nin one file — `deepseek*` was bucketed self-hosted while priced from the published\nDeepSeek *API* rate card, and the roster above binds `qwen/qwen3.6-27b` to **Groq**, a\nvendor, while the classifier called those tokens self-hosted.\n\nA model id now buys only a model-family answer (`open-weights (placement unknown)`), and\nplacement comes from a real signal:\n\n| Call | Answers |\n| --- | --- |\n| `ProviderBucket(model)` | whose *model* it is — never where it ran |\n| `BucketForPlacement(model, zone)` | the bucket, given a `PlacementZone` string |\n| `BucketIsSelfHosted(bucket)` | `(selfHosted, known bool)` — two values on purpose |\n\n`BucketIsSelfHosted` returns a second `known` bool so unattributable volume cannot be\nfolded into either side of a self-hosted fraction. `TestPricedModelsAreNeverBucketedSelfHosted`\nmakes the underlying rule executable and total over the rate card: a model billed per token\nby a third party ran on their hardware, so it can never also be bucketed as self-hosted.\n"}, "ba6cefea97eb4bfcb179c06a05ee190c4c84c833": {"t": 1786068473.405948, "n": 43276, "h": "6167b7aaac2b701b", "c": "---\ntitle: \"fak account switcher: bring your own model accounts\"\ndescription: \"How fak's account switcher routes each aspect of a request to a chosen account and provider wire — OpenAI, Codex, Anthropic, Gemini, DeepSeek, or local — so you can mix providers per tool call, step, or ensemble half.\"\n---\n\n# The account switcher: bring your own accounts, mix and match\n\n`fak route` decides *which* model — or which ensemble of models — serves an aspect of\na request. The account switcher decides *whose account* runs that model, over which\nprovider's wire. Together they let you point fak's routing at your own OpenAI, Codex,\nAnthropic, Gemini, Groq, DeepSeek, and local accounts, and mix providers at any level: the cheap\naspect to a local model, the hard reasoning step to your OpenAI account, and a\ntwo-model guard ensemble whose halves run on two *different* accounts.\n\nIt is the generic, in-product sibling of the fleet account switcher (`fak fleet-accounts`,\nthe native Go successor to the legacy `tools/fleet_accounts.py` shim): provider-neutral,\ncredential-safe, and composable with the routing spine. It lives in\n`internal/modelroute/account.go` (pure, stdlib-only, the same package as the routing\ndecision and the cost lens).\n\n## The pieces\n\n**Account** — the switcher unit. A named credential set for one provider. Two\naccounts can target the *same* provider kind (`openai-personal` and `openai-work`),\nwhich is the switch: you choose which credential serves a model. An account names the\nenv var that holds its key (`cred_env`); the secret itself never lives in the file.\nOpenAI-compatible providers such as Groq are represented as `kind: \"openai\"` plus their\nprovider `base_url`, so the wire adapter stays generic while the account id names whose\ncredential is being used.\n\n**Binding** — maps one routed model id (a `small`/`large`/`guard-a` from your routing\nmanifest, or a plan's scout) to an account plus the upstream model name to send on the\nwire. The routed id is an abstract tier label; the upstream model is the\nprovider-specific name.\n\n**Roster** — the declarative, version-tagged JSON manifest holding your accounts and\nbindings, plus a default account for any unbound id. It loads the same way the routing\nmanifest does: `DisallowUnknownFields`, fail-loud validation, round-trips\n`--accounts-dump` ↔ `--accounts-check`.\n\n**Target** — the resolved destination for one model id: the account, the provider\nkind, the concrete base URL, the credential env-var name, and the upstream model. The\ndispatch layer turns a Target into a live planner; the resolver itself does no I/O.\n\n```jsonc\n{\n \"version\": \"fak-accounts/v1\",\n \"accounts\": [\n { \"id\": \"local\", \"kind\": \"local\", \"base_url\": \"http://127.0.0.1:11434/v1\" },\n { \"id\": \"openai-personal\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"openai-work\", \"kind\": \"openai\", \"cred_env\": \"OPENAI_WORK_API_KEY\" },\n { \"id\": \"codex\", \"kind\": \"openai-responses\", \"cred_env\": \"OPENAI_API_KEY\" },\n { \"id\": \"claude-sub\", \"kind\": \"anthropic\", \"cred_env\": \"CLAUDE_CODE_OAUTH_TOKEN\" },\n { \"id\": \"july6netra_groq\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 1000,\n \"tokens_per_minute\": 8000, \"tokens_per_day\": 200000 },\n { \"id\": \"july6netra_groq_compound\", \"kind\": \"openai\",\n \"base_url\": \"https://api.groq.com/openai/v1\", \"cred_env\": \"FAK_GROQ_API_KEY\",\n \"requests_per_minute\": 30, \"requests_per_day\": 250 },\n { \"id\": \"deepseek\", \"kind\": \"deepseek\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 },\n { \"id\": \"deepseek-anthropic\", \"kind\": \"anthropic\",\n \"base_url\": \"https://api.deepseek.com/anthropic\", \"cred_env\": \"DEEPSEEK_API_KEY\",\n \"context_tokens\": 1000000, \"max_output_tokens\": 384000 }\n ],\n \"default\": \"openai-personal\",\n \"bindings\": [\n { \"model\": \"small\", \"account\": \"local\", \"upstream_model\": \"llama3.2\" },\n { \"model\": \"guard-a\", \"account\": \"openai-work\", \"upstream_model\": \"gpt-5.5\" },\n { \"model\": \"guard-b\", \"account\": \"claude-sub\", \"upstream_model\": \"claude-opus-4-6\" },\n { \"model\": \"qwen36-groq\", \"account\": \"july6netra_groq\", \"upstream_model\": \"qwen/qwen3.6-27b\" },\n { \"model\": \"groq-compound\", \"account\": \"july6netra_groq_compound\", \"upstream_model\": \"groq/compound\" },\n { \"model\": \"deepseek-pro\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-pro\" },\n { \"model\": \"deepseek-flash\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-v4-flash\" },\n { \"model\": \"deepseek-chat-compat\", \"account\": \"deepseek\", \"upstream_model\": \"deepseek-chat\",\n \"compatibility_only\": true, \"deprecated_after_utc\": \"2026-07-24 15:59 UTC\",\n \"deprecated_alias_for\": \"deepseek-v4-flash non-thinking mode\" }\n ]\n}\n```\n\nA full example is `examples/model-accounts.example.json`.\n\n## Two account layers, one vocabulary\n\nThis page is about **provider accounts**: which credential/env-var serves a routed model.\nCoding-agent subscriptions add a second layer: **config-home accounts**. Claude uses\n`CLAUDE_CONFIG_DIR` homes such as `~/.claude-gem8-seat`; Codex uses `CODEX_HOME` homes such as\n`~/.codex` or `~/.codex-work`. `fak fleet-accounts` discovers both, derives their non-secret\nprovider-account identity, collapses duplicate homes on the same rate-limit bucket, and offers\nonly homes whose credential state is ready. The default Codex picker profile is\n`gpt-5.6-sol` with `model_reasoning_effort=xhigh`.\n\nThe authored lifecycle registry behind `fak accounts` remains Claude-specific today; its seat\nnames and tombstones are therefore applied only to Claude rows. Codex rows take lifecycle/login\ntruth from their own home (`auth.json`/`config.toml`) and never inherit a same-named Claude seat\nsuch as `default`.\n\nUse `fak accounts status --json` for the observable config-home login report. It emits\n`fak.accounts.login.v1`: one closed `status` per seat (`ready`, `needs_login`, `missing_dir`,\n`disabled`, `tombstoned`), `can_serve`, roles, warnings (`duplicate_account_bucket`,\n`split_setup_token`, `unverified_account`), and a next action. The human `fak accounts list` table\nshows the same status in its `LOGIN` column, and `fak accounts sync` materializes\n`login_status` plus `can_serve` into the generated dos/job roster rows. That keeps the account\nswitcher from guessing at login readiness from directory names or scattered credential booleans.\nThe same vocabulary is carried through `fak fleet-accounts roster/resolve`, `fak dispatch\ntick/wave`, `fak accounts launch`, `fak accounts next`, and `fak guard` auth warnings, so callers\ncan gate on `can_serve` and surface the closed login status instead of re-deriving readiness from\nraw credential files.\n\n## Mix and match at any level\n\nThere is no per-aspect special case. The routing decision produces model ids for the\nwhole request, a tool call, a reasoning step, a scout probe, or each member of an\nensemble — and the roster binds *every* one of them by id. So an ensemble can span\naccounts and providers (`guard-a` on your OpenAI work account, `guard-b` on your\nAnthropic subscription), and the cheap scout-classify probe can switch accounts\nindependently of the members it gates.\n\n```\nfak route --manifest examples/model-routing.example.json \\\n --aspect tool_call --tool refund_payment \\\n --accounts examples/model-accounts.example.json\n```\n\nprints the routed guard ensemble *and* the account each member resolves to.\n\n## Credentials are references, never secrets\n\nAn account names an env var (`cred_env: OPENAI_API_KEY`); the key is read with\n`os.Getenv` only at dispatch time, in the layer that builds the planner. The roster, the\nresolved Target, the `EngineRoute`, and every `--accounts` / `--accounts-dump` output\ncarry the *name*, never the value. Validation enforces this: a `cred_env` that is not a\nvalid env-var name — a pasted `sk-ant-…` key, a `Bearer …` string, an `X=Y` pair — is\nrejected at the boundary, so a real key cannot end up committed in a roster.\n\nUse `fak route --accounts-status roster.json` to inspect provider-account readiness in the\ncurrent shell. It emits `fak.modelroute.accounts.v1`: local accounts are `not_required`; remote\naccounts are `ready` only when their named env var is present and non-empty, otherwise\n`needs_credential` with the env var to set. This is intentionally an environment observation, not\na live API probe or billing claim, and it still prints only env-var names.\n\n## API-host probe bridge\n\nThe same model-account roster can feed the API-host no-spend probes:\n\n```bash\nfak api-host readiness --from-model-accounts examples/model-accounts.example.json\nfak api-host acceptance --from-model-accounts examples/model-accounts.example.json\n```\n\n`readiness` converts only OpenAI-compatible or local accounts into `/models` probe targets\n(`openai`, `openai-responses`, `xai`, `deepseek`, and `local`). Native provider accounts are not guessed into\nan OpenAI-shaped probe. `acceptance` keeps every account with a probeable base URL visible:\nOpenAI-compatible/local accounts can become `READY_FOR_LIVE_BRIDGE_RUN`, while native\nAnthropic/Gemini accounts are reported as `WIRE_SUPPORTED_UNPROBED`. Model hints come from the\nroster bindings, so the API-host report stays tied to the same abstract model ids the route policy\nuses.\n\nGroq Qwen3.6 uses the OpenAI-compatible base `https://api.groq.com/openai/v1`, credential\nenv var `FAK_GROQ_API_KEY`, and upstream model slug `qwen/qwen3.6-27b`. The example records\nthe current account/model limits as metadata: 30 requests/minute, 1,000 requests/day,\n8,000 tokens/minute, and 200,000 tokens/day. Groq Compound uses the same base and env var\nunder the separate account id `july6netra_groq_compound`, bound as the lower-quality\n`groq-compound` target to upstream slug `groq/compound`; its metadata is request-only:\n30 requests/minute and 250 requests/day, with no token-minute or token-day cap recorded.\nThe Claude dogfood launcher separately caps the outbound `max_tokens` field at 8192 for\n`groq/compound`, because Groq rejects larger per-response output budgets. Those values\nare advisory metadata for routing and launchers; the v1 resolver does not throttle\nrequests.\n\nDeepSeek V4 uses `deepseek-v4-pro` / `deepseek-v4-flash` on the OpenAI-compatible base\n`https://api.deepseek.com`; its Anthropic-compatible profile uses\n`https://api.deepseek.com/anthropic`. The legacy `deepseek-chat` and `deepseek-reasoner`\naliases are accepted only when marked `compatibility_only`, with the documented retirement\nmetadata `2026-07-24 15:59 UTC`, so a new roster does not silently bind to an alias that is\nabout to disappear.\n\n## Residency is declared, not guessed\n\nfak's residency floor denies a tenant-scoped or sensitivity-tagged payload bound for a\n*remote* engine. It reads the route string written to `abi.ToolCall.Engine`.\n`Target.EngineRoute()` stamps that string with a structural prefix taken from the\naccount's kind: a local target is `local:…` (the floor reads it as on-box and exempt),\na remote target is `:…` where the kind is one of the keywords the floor\nrecognizes. Locality has one source of truth — `kind == local` — so it can never\ndisagree with a second flag the floor might trust. Validation forbids a local account\nfrom carrying a non-loopback base URL, which would otherwise emit a `local:` route\nwhile the bytes egress off-box. A cross-package test\n(`internal/engine/account_residency_test.go`) pins that the floor and the switcher\nagree for every provider kind, so a future kind that the floor could not classify is a\nbuild-time failure, not a silent fail-open. The same file pins the tier-1 mirror\n(`modelroute.IsRemoteRoute`) against the enforcing floor over one corpus, because\n`internal/modelroute` sits below `internal/engine` and must keep its own copy of the\non-box family list. See *Placement zones* below for the third rung the binary\nlocal/remote split does not name.\n\n## Placement zones: your box, your company's boxes, someone else's\n\nLocality is not binary. A company that self-hosts runs models in **three** places, and\nan account declares which by its `kind`:\n\n| Zone | `kind` | Where the weights sit | Self-hosted? | On-box? |\n| --- | --- | --- | --- | --- |\n| `device` | `local` | the engineer's own machine (loopback only) | yes | **yes** |\n| `fleet` | `fleet` | a machine the **organization** operates | yes | no |\n| `vendor` | `openai` / `anthropic` / `gemini` / `xai` / `deepseek` / `openai-responses` | a third-party lab's API | no | no |\n\nThe middle rung is the one that carries the token volume in a self-hosting shop — a\nGLM- or Kimi-class open model on company GPUs, shared by every engineer. Before\n`kind: fleet` existed it could not be written down: as `local` it was refused (a local\naccount must carry a loopback base URL), and as `openai` it was admitted but became\nindistinguishable from `api.openai.com` in every downstream record, so the org got no\ncredit for hardware it owns.\n\n```json\n{\n \"id\": \"corp-glm\",\n \"kind\": \"fleet\",\n \"base_url\": \"http://glm.infer.corp.internal:8000/v1\",\n \"label\": \"GLM-5.2 vLLM server the company operates\"\n}\n```\n\nA `fleet` account needs an explicit, non-loopback `http(s)` endpoint (there is no public\ndefault for a host only your org can name, and a loopback address is `device` by\ndefinition — the zones must stay disjoint). Unlike a vendor account it does **not**\nrequire a `cred_env`: an org-operated server on a private network commonly has no API\nkey. Supply one when the endpoint authenticates.\n\n**Self-hosted is not the same as on-box, deliberately.** `ZoneFleet.SelfHosted()` is\ntrue — its tokens count toward \"we ran this on our own silicon\" — but\n`ZoneFleet.OnBox()` is false, so the residency floor still treats a `fleet:…` route as\nremote and still denies a tenant-scoped payload routed there. Naming the zone changes\nwhat fak can *attribute*, not what it *permits*. Letting an org-operated host carry a\nsensitive payload is an enforcement change that needs an operator-declared trust\nboundary (authenticated transport, a named host allowlist), and it is tracked\nseparately — it does not arrive as a side effect of declaring a zone.\n\nThe zones are ordered as an escalation ladder — `device` (0) → `fleet` (1) →\n`vendor` (2) — so \"prefer the cheapest rung that can do the work\" is a comparison on a\ndeclared value rather than a string switch. An unrecognized zone ranks *above* vendor\nand is neither self-hosted nor on-box: unattributable never reads as free or as safe.\n\n### Walking the ladder: `Roster.Place`\n\n`Roster.Place(class, candidates)` is what actually walks it. Given a `WorkClass` and the\nmodels you have bound — each with the work tier you have evidence it can serve — it\nreturns the cheapest rung that can take the job, plus the full ladder walk that produced\nthe answer:\n\n```go\np, err := roster.Place(modelroute.ClassRoutine, []modelroute.Candidate{\n {Model: \"tiny\", Capability: modelroute.TierT2, Measured: true},\n {Model: \"corp-mid\", Capability: modelroute.TierT1, Measured: true},\n {Model: \"frontier\", Capability: modelroute.TierT0, Measured: true},\n})\n// p.Zone == ZoneDevice, p.SelfHosted() == true, p.Escalated == false\n```\n\nTwo rules stop it from being wishful thinking:\n\n**The floor belongs to the work, not to the model.** Admission runs through\n`TierPolicy.Admit`, so a cheap rung cannot take work above its tier however available it\nis. Security/release/destructive work has a floor that never drops to routine, so it\nskips a measured local model and lands on the fleet — `p.Escalated` is true and the\ndevice rung reports `zone-under-tier`. An unrecognized class stays at the strictest floor\nrather than inferring a cheap one.\n\n**Unmeasured capability may not descend the ladder.** `Candidate.Measured` records\nwhether `Capability` is a measurement or an assertion, and an assertion is skipped on\nevery rung below the vendor. This matters right now: `internal/ablate`'s `StubTierScorer`\nmeasures nothing, so a placer that trusted asserted grades would route everything to the\ncheapest rung on a number nobody computed, and would look like it was working. Until\ncapability measurement lands, an operator opts a rung in by marking a candidate\n`Measured` — a claim they can be held to. `TestUnmeasuredCapabilityCannotDescendTheLadder`\npins both halves: an all-asserted ladder places on the vendor, and the same work moves to\nthe device the moment the cheap rung is measured.\n\nEvery rung is reported, including the ones passed over, in a closed reason vocabulary\n(`placed-in-zone`, `zone-under-tier`, `zone-capability-unmeasured`, `zone-no-candidate`,\n`zone-not-reached`, `escalated-past-cheaper-zone`) — because \"why is this *still* going to\na vendor?\" is the question an operator actually asks. A candidate the roster cannot\nresolve is a loud error, never a silent skip: a typo in a placement config must surface as\na misconfiguration rather than as traffic quietly continuing to bill a vendor.\n\nThe trap this code is written around is that `WorkTier` numbers are **inverted** — T0 is\nthe most demanding but the lowest number. Every comparison goes through\n`WorkTier.MeetsRequirement`, never a raw `<` or `>=`. Substituting the natural-looking\n`capability >= required` fails four tests at once, and the way it fails is the point: a\n4B laptop model takes both the ultra-hard and the security/destructive work.\n\n### Declaring what the work is\n\n`Place` needs a `WorkClass`; routing produces a `Subject`. `ClassOf(Subject)` is the\njoint, and it is where an automatic placer is most tempted to cheat — it would be easy to\nlook at a tool name, decide \"grep is harmless\", and route it to a laptop. fak does not do\nthat. Judging what a call can destroy is `internal/adjudicator`'s reversibility question,\nit sits a tier above this leaf, and it is deliberately not re-guessed from a name here.\n\nSo a work class is **declared, or it is conservative**. Declare it as a subject label:\n\n```jsonc\n{ \"match\": { \"aspect\": \"tool_call\", \"labels\": { \"work_class\": \"routine\" } },\n \"plan\": { \"model\": \"tiny\" } }\n```\n\n| Input | Classified as | Why |\n| --- | --- | --- |\n| `labels.work_class` naming a known class | that class, **declared** | the operator said so |\n| `aspect: \"scout\"`, nothing declared | `routine`, **declared** | a scout *is* the cheap classify-first probe — the aspect names the work |\n| anything else | *(empty)*, **undeclared** | `PolicyFor` already puts the empty class at the strictest floor |\n| `labels.work_class` misspelled | *(empty)*, **undeclared** | a typo in `routine` must not read as permission to use a laptop |\n\nUndeclared work therefore lands exactly where it landed before this feature existed — on\na vendor — and reports `class-undeclared-conservative` so an operator can see the fix is\na label rather than more hardware. There is no second conservatism mechanism layered on\ntop; one gate in one place is why the behaviour stays predictable. `Complexity` can then\nratchet a floor **up** (a high-complexity `routine` subject becomes `normal-impl`) and\nnever down, which is what makes accepting complexity as an input safe at all.\n\n`Roster.PlaceSubject(subject, candidates)` composes the two and returns the placement\n*and* the classification, so both halves of the decision are visible together.\n\n### Declaring what a sub-agent type does\n\nSub-agents are where the cheap rungs would pay for themselves — a fleet spawns far more\ndelegated work than top-level turns — and `Roster.PlaceSpawn(parent, class, candidates)`\nalready answers \"which rung should this child run on\". It refuses an **empty** work class\non purpose: an unclassified spawn is a missing classification, not a routine one, and\nletting delegation reach a laptop without stating what the work *is* would be exactly the\nshape a floor bypass takes.\n\nThat refusal is what left the call unreachable. A spawn arrives as an admitted tool call\ncarrying an agent **type** and a prompt. The prompt is prose, and reading a class out of\nprose is the guess `ClassOf` refuses everywhere else. The type is structured — but it is\nnot a work class: only an operator knows whether the agent type *their* fleet calls\n`explore` does bounded lookup or ships code.\n\nSo it is declared, in the roster the operator already owns:\n\n```jsonc\n\"spawn_classes\": [\n { \"type\": \"explore\", \"class\": \"routine\" },\n { \"type\": \"code-reviewer\", \"class\": \"normal-impl\" },\n { \"type\": \"release-cutter\", \"class\": \"security-release-destructive\" }\n]\n```\n\n`Roster.SpawnClassFor(type)` resolves one, returning `(class, declared bool)`. The rules\nare the ones the rest of this leaf runs on:\n\n| Input | Resolves to | Why |\n| --- | --- | --- |\n| a declared type (case- and space-insensitive) | that class, **declared** | the operator said so |\n| a type nobody declared, or no `spawn_classes` block at all | *(empty)*, **undeclared** | `PlaceSpawn` then refuses — the spawn keeps whatever placement it has today |\n| a declared type whose class token this package does not know | *(empty)*, **undeclared** | same rule as a misspelled `work_class` label: a typo is not permission to use a laptop |\n\nThe match is **exact**, never a prefix and never a glob — `explore` must not answer for\n`explore-and-delete`. A malformed entry is refused at load rather than skipped: an empty\ntype, an empty or unknown class (the error names the four options), a duplicate type, or a\ntype carrying a route delimiter. A silently-dropped entry is the worst outcome available,\nbecause it looks correct from the operator's side while behaving like an undeclared one,\nand nothing points at the typo.\n\n**Shipped:** the declaration, its validation, and the resolver — plus the block in\n`examples/model-accounts.example.json`, which the test suite parses and resolves.\n**Not shipped:** nothing on the spawn path calls `SpawnClassFor` yet. The gateway does\nobserve spawns (`internal/gateway/adjudicate_proposed.go` stamps every admitted tool call\nand already recognises the spawn-shaped ones — that is where `spawn_count` comes from),\nand that is the seam a placement call would bind to, but binding it changes where\ndelegated traffic runs and is a separate, individually reviewable change under\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416). Declaring\n`spawn_classes` today moves no traffic.\n\nOne neighbouring field is *not* this signal, and it has already been mistaken for it: a\nsession's `ParentTrace`/`Generation` is **continuation** lineage — the same agent after a\nbudget-reset re-continuation — written only by `session.Table.Recontinue`. It never marks\na row as somebody's child. `SpawnCount` is the sub-agent axis, and it is parent-side: it\ncounts children a trace spawned, never whose child a trace is.\n\n### Seeing the ladder before it moves traffic: `fak route --place`\n\n```console\n$ fak route --accounts accounts.json --place --labels work_class=routine\n```\n\nThe oracle walks the ladder for one subject against your real roster and prints the rung\nit chose, every rung it passed over, and the closed reason token for each. It exists\n*before* anything on the dispatch path calls `Place`, deliberately: a placement policy\nnobody can inspect is a policy nobody can be held to, and an operator has to be able to\nsee what the ladder would do with **their** roster before fak starts moving traffic on\nthe strength of it. Add `--json` for the same answer as a report.\n\nThe candidate pool is every model the roster **binds**, not just the one routing picked —\nthe question is which rung can serve this class of work at all. Compatibility-only and\ndeprecated-alias bindings are excluded, since admitting a legacy spelling would let the\nsame hardware appear twice on a rung.\n\nAdd `--spawn-type TYPE` to ask the *delegated* question in the same breath: where would a\nsub-agent of that type run, given that this turn ran where the block above says. Against\nthe shipped example roster:\n\n```console\n$ fak route --accounts examples/model-accounts.example.json --place \\\n --labels work_class=ultra-hard --spawn-type explore \\\n --capability zone-device=t2,zone-fleet=t1,large=t0\n```\n```\nSPAWN PLACEMENT (--spawn-type explore)\n work class routine [declared by the roster's spawn_classes]\n parent zone=vendor model=deepseek-flash\n placed zone=device model=zone-device\n self-hosted yes escalated no failed-over no\n relation spawn-descended-from-parent-zone spawn-inherit-unmeasured\n descent yes self-hosted descent yes\n```\n\nThe child's class comes from the `spawn_classes` declaration above — never from the\nparent, the prompt, or the spelling of the type — so an **undeclared** type is refused\nwith the roster's own list of declared ones rather than assumed routine. The parent's rung\nis *recorded, not obeyed*: a sub-agent spawned from a frontier turn still lands on the\nlaptop, which is the whole point, and `self-hosted descent` is the event\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416) counts. The report also\nanswers the counterfactual — what *inheriting* the parent's model would have done — and\nsays `UNKNOWN` when the parent was never graded, because \"the status quo was fine here\"\nis a claim that needs a measurement behind it. `--json` carries the same answer under a\n`spawn` key that is **absent** when the question was not asked.\n\n### Saying what a model can do\n\nBecause unmeasured capability may not descend the ladder, a fresh roster places\n*everything* on the vendor rung — correctly, and uselessly. There are three ways to give\nthe ladder something to descend on, in increasing order of what they cost to produce:\n\n| Flag | What it is | What it costs to be wrong |\n| --- | --- | --- |\n| `--capability qwen3.6-4b=t2,glm-5.2=t1` | an operator **asserting** a grade | attributable to a person, but does not scale past a handful of models |\n| `--evidence FILE` | a summary of observed outcomes, graded here | someone assembled the counts; you are trusting their arithmetic |\n| `--outcomes FILE` | the append-only turn journal, **counted here** | nothing is trusted but the record itself |\n\nAll three feed one grader (`modelroute.GradeCapability`), and its bar is yours:\n\n```console\n$ fak route --accounts accounts.json --place --labels work_class=routine \\\n --outcomes turns.jsonl --since 30d --grade-floor attempts=50,rate=0.9,witness\n```\n\nThe default bar is 20 independently verified attempts at 80%. There is deliberately **no\nknob that makes self-report count** — a model's own claim of 500/500 successes buys\nnothing, and the refused attempts are reported *as refused* rather than as absent. An\noperator who wants to assert a capability can already do so, attributably, with\n`--capability`. A model claimed by both an assertion and a measurement is a named\nrefusal: they cannot both be the grade.\n\nA grade is the **floor** of the work observed, never its optimal tier, and a model that\nfails the bar is `UNMEASURED` **with a reason** rather than graded at the worst tier. The\nthree reasons call for opposite responses and are never collapsed into one:\n\n| Reason | The fix |\n| --- | --- |\n| `no-trusted-evidence` | run the turns under a witness or a judge — the volume is there, the provenance is not |\n| `insufficient-samples` | run more turns, or lower `attempts=` if you meant to |\n| `below-success-floor` | this model cannot do this work; that is the answer |\n\n### Grading from the turn journal\n\n`--outcomes` reads the record itself — one JSON object per line, appended as turns\nhappen (`modelroute.TurnOutcome`):\n\n```jsonl\n{\"id\":\"t-4471\",\"model\":\"qwen3.6-4b\",\"class\":\"routine\",\"zone\":\"device\",\"success\":true,\"verify\":\"witness\",\"at\":\"2026-07-24T11:02:00Z\"}\n```\n\nThe fold is written to **lose** evidence rather than invent it, because each alternative\ninflates a claim, and it reports every loss:\n\n```\n journal turns.jsonl: 1463 line(s), 1332 counted, 4 model(s) with evidence\n not counted 1 unparseable line(s), 30 replayed id(s), 60 older than 30d, 40 undated\n```\n\n- A repeated `id` is counted **once**. Replaying a file is the cheapest possible way to\n manufacture a grade: the same 20 successes appended three times must not read as 60.\n- An **undated** row cannot be shown to be inside a window, so `--since` excludes it — and\n counts it apart from a stale one, because one needs a wider window and the other needs a\n producer that stamps its rows. Ask for no window and a missing date costs nothing.\n- **Provenance is never merged.** 100 self-reported turns and 20 witnessed ones about the\n same model stay two rows; merging them would force a pick of provenance, and any pick\n is a lie. The grade keeps the weakest provenance behind whatever it merged.\n- A **torn line** is skipped and counted, never fatal. A fleet appending to this file will\n eventually die mid-write, and discarding thousands of verified turns over one partial\n row would make the honest path the fragile one.\n- Rows with no `id` are kept — a missing id is not proof of a duplicate — but reported as\n a corpus that **cannot be checked for replay**, since that is a fact about how much the\n number is worth.\n\n`--evidence` and `--outcomes` are mutually exclusive: a run that took some of its answer\nfrom a journal and some from a hand-written summary produces a grade whose provenance\nnobody can state afterwards.\n\n**What is not shipped:** nothing yet *writes* this journal. `internal/ablate`'s\n`StubTierScorer` still grades nothing, so today the file is something you produce — from\nDOS's git witness, a judge score, or your own harness. That producer is the one piece\nstanding between this ladder and traffic that moves by itself.\n\n### Placing against what is actually answering\n\nA grade says what a model *can* do. It says nothing about whether the box is powered on.\n`--serving FILE` hands the ladder a liveness snapshot — what your own probes just saw —\nand the ladder walks past candidates it reports are not serving:\n\n```json\n{\n \"schema\": \"fak.modelroute.serving.v1\",\n \"as_of_unix\": 1785312000,\n \"max_age_seconds\": 120,\n \"covers\": [\"device\", \"fleet\"],\n \"models\": {\n \"zone-device\": {\"state\": \"up\", \"observed_unix\": 1785311950},\n \"zone-fleet\": {\"state\": \"down\", \"observed_unix\": 1785311950},\n \"zone-fleet-agentic\": {\"state\": \"up\", \"observed_unix\": 1785311950}\n }\n}\n```\n\n```console\n$ fak route --accounts examples/model-accounts.example.json --place \\\n --labels work_class=normal-impl --serving serving.json \\\n --capability zone-device=t2,zone-fleet=t1,zone-fleet-agentic=t1,large=t0\n```\n```\n placed zone=fleet model=zone-fleet-agentic\n self-hosted yes escalated yes failed-over yes\n ladder\n device - zone-capability-unmeasured x2 zone-under-tier\n fleet zone-fleet-agentic zone-serving-down placed-in-zone escalated-past-cheaper-zone\n vendor - zone-not-reached\n\nSERVING SNAPSHOT (--serving serving.json)\n as of 1785312000 max age 120s\n covers device fleet\n observations 3, of which 3 name a model this roster binds\n SILENT small tiny-classifier\n the snapshot claims to speak for these candidates' rungs and then says\n nothing about them, so they are passed over as unknown rather than\n assumed well. That is a gap in the probe, not an outage.\n```\n\n**One dead GPU host is not a dead rung.** A fleet is several machines, and abandoning the\nrung because one box rebooted sends every token to a third-party lab to route around a\nneighbour that was idle — so the failover above stayed on the company's own hardware.\n\n| Verdict | What it means | Placed? |\n| --- | --- | --- |\n| `zone-serving-down` | observed unable to serve | passed over |\n| `zone-serving-unknown` | no verdict, on a rung the snapshot *claims* to cover | passed over |\n| `zone-serving-stale` | a verdict that cannot be shown fresh | passed over |\n| `zone-serving-degraded` | serving under strain | **recorded, and placed anyway** |\n\nDegraded is deliberately not actionable: a loaded host still takes work, and shedding at\nthe first sign of queueing inverts self-hosting exactly in the busy hour it is meant for.\n\nThree rules decide the rest, and each is fail-closed in a different direction:\n\n- **Silence only means something where you said it would.** `covers` is the operator\n declaring which rungs the probe actually watches; a candidate the snapshot is silent\n about *inside* that coverage is unknown, not healthy, because a crashed prober must not\n read as a fleet in perfect health. Outside coverage, silence gates nothing.\n- **An observation is always honored**, coverage or not. Coverage governs what silence\n means, never what a verdict means — a report that says a host is down is not overruled\n by a `covers` list that forgot to mention its rung.\n- **Freshness is fail-closed, and so is every way of not having it**: no observation\n stamp, no `as_of_unix`, past `max_age_seconds`, or stamped *after* the report all read\n as stale. A snapshot with a bound and no clock to measure against therefore gates\n everything it covers, which is correct and reads exactly like a total outage — so the\n summary says `STALE-ALL` and names the missing field rather than leaving you to guess.\n\n**`failed-over` is carried apart from `escalated`** because they want opposite fixes.\nEscalated says a cheaper rung could not do this work — a capability fact, stable until\nsomeone changes the roster. Failed-over says a rung that *could* have was routed around\nbecause something was not answering — an operations fact that may already be untrue. A\nbill read without that second bit shows vendor spend attributed to work that \"needed\" a\nvendor, and sends an operator hunting a capability problem that does not exist while the\ndead host stays dead. Both are printed for every placement, including the ones that stayed\ncheap: a failover *within* a rung cost nothing and still means something is down.\n\nThe snapshot and the roster are validated apart and meet only here, so the report also\nnames what neither half can see alone:\n\n```\n observations 2, of which 0 name a model this roster binds\n UNBOUND glm-5.2 kimi-k3\n these observations gate NOTHING: the ladder only ever asks about\n models the roster binds, so a probe filed under an id nothing binds\n is honored nowhere and this run is identical to one with no snapshot.\n Check the ids against the roster's bindings, not its upstream names.\n Each of these is the UPSTREAM name of a candidate: the probe filed its\n result under the name it dialled, and the ladder asks by routed id.\n glm-5.2 -> zone-fleet\n kimi-k3 -> zone-fleet-agentic\n```\n\nThat is the fail-open the flag would otherwise ship: a clean-validating report, an\noperator who believes they gated a dead host, and a placement identical to one with no\nsnapshot at all. The same typo *inside* declared coverage fails the other way instead —\nthe real candidate is now silent on a rung the report speaks for, so it is passed over as\nunknown and a healthy host looks dead. One id appearing in `UNBOUND` and its routed name\nin `SILENT` is the signature of exactly this mistake. Where an upstream name resolves to\n**two** candidates, both are named: two accounts serving the same weights is how a rung\nscales, and one observation cannot speak for both pools — which is why the snapshot is\nkeyed by routed id in the first place.\n\nTwo smaller refusals hold the same line. An unknown field is rejected rather than ignored,\nbecause `max_age_sec` otherwise parses, validates, looks right, and silently switches\nfail-closed freshness *off*. And a named file that cannot be read is an error, never an\nempty report: dropping the flag means \"no liveness signal\", while a missing file means\nyour probe did not run, and placing work as though every host were healthy is precisely\nthe failure the gate exists to prevent. In `--json` the `serving` key is **absent** unless\na snapshot was supplied, so it can never be misread as a probe that saw nothing.\n\n**What is not shipped:** nothing produces this file either. It is deliberately not a probe\nof fak's own — placement is asserted to be pure (same roster, same candidates, same\nreport, same answer), and a CLI that dialled endpoints would be reading a clock and a\nnetwork inside a decision that reads neither. The snapshot is your monitoring's output,\nin fak's vocabulary.\n\n## A note on codex and Anthropic subscriptions\n\nCodex's native wire is the OpenAI Responses API, so bind it to `kind: openai-responses`,\nnot plain `openai`. (`fak guard -- codex` autodetects the chat-completions `openai`\nwire today; the roster lets you pick the Responses wire explicitly.)\n\nAn Anthropic Pro/Max subscription token (`sk-ant-oat…`) rides as\n`Authorization: Bearer` plus the `oauth-2025-04-20` beta header, not as `x-api-key`.\nThe roster declares the account and the env var that holds the token; the Bearer-vs-key\nheader choice is made downstream by the Anthropic adapter at dispatch.\n\n## Managed-cache posture across launchers\n\nEvery fak launcher that fronts an agent with `fak guard` — `fak accounts launch`,\n`fak codex`, and the dispatch worker (`fak dispatch tick`/`auto`) — can hand guard a\n**managed-cache posture** that decides whether the gateway upgrades the stable-prefix\n`cache_control` breakpoint to the **1h-TTL** tier on the outbound Anthropic wire.\n\n**A subscription seat stays passive by default, on purpose.** Guard's own `--managed-cache\nauto` follows a never-speculate rule: it will not manage a wire whose billing it cannot see.\nA Pro/Max seat authenticates with subscription OAuth (`Authorization: Bearer`), whose cache\neconomics guard does not own, so `auto` resolves **passive** there and no launcher silently\nflips it. Nothing changes for a subscription fleet unless you ask for it.\n\n**An API-key-billed fleet reaches ACTIVE without hand-editing any launcher**, via two env\nknobs read once per launch and threaded onto the child's guard argv (not its environment, so\na *resumed* child — whose gateway env is deliberately stripped — keeps the posture):\n\n| Knob | Effect |\n|------|--------|\n| `FAK_GUARD_API_KEY_ENV=ANTHROPIC_API_KEY` | Names the env var holding the API key. On the Anthropic wire this is the explicit opt-IN to API billing that lets `auto` resolve **ACTIVE** (guard bills the key, not the seat's subscription). |\n| `FAK_MANAGED_CACHE=on\\|off\\|auto` | Forces the posture. `on` upgrades regardless of billing; `off` disables; `auto` (default) defers to the billing signal above. |\n\n`fak accounts launch --managed-cache ` and `fak codex --managed-cache ` expose\nthe same lever per-launch (defaulting to `$FAK_MANAGED_CACHE`); the dispatch worker reads the\nenv knobs only. An unknown mode fails loud at the front-ends; the headless worker warns and\nfalls back to `auto` so a fleet turn never dies over a cache-posture typo. Either way the\nguard child prints the RESOLVED posture in its startup banner, so the worker log witnesses\nwhether the 1h upgrade actually engaged. An unconfigured fleet emits no `--managed-cache`\nflag at all — the guard argv is byte-identical to before this knob existed.\n\n## What is shipped, and what is not\n\n**Shipped:** the resolver. A roster resolves a routed plan — the scout and every\nmember, in member order — to per-member Targets, with fail-loud validation and the\nresidency-honest `EngineRoute`. The `fak route --accounts` path prints the binding.\nThis is pure and deterministic, witnessed by `go test ./internal/modelroute` and the\ncross-package residency test.\n\n**Deferred:** live multi-account dispatch\n([#2528](https://github.com/anthony-chaudhary/fak/issues/2528)). v1 *resolves and prints*\na binding; it does not yet send a request over the wire to your chosen account. Building\nthe planners from the Targets and running an ensemble's members is the additive dispatch\nwiring, the same shipped-vs-deferred split the routing spine uses for the ensemble fold\nversus its execution. (Per-call routing into dispatch — writing the routed id to\n`abi.ToolCall.Engine` before submit — has landed for the single-model case; the\naccount-resolved route is the next refinement of what that field carries.)\n\n**Out of scope for v1:** this is a declarative, static binding resolver. It has no\nper-account rate-limit, capacity, health, or failover signal. It is not the fleet's\nload-aware switcher — it is the portable, reviewable account map that a switcher would\nconsult.\n\n**Placement zones — shipped and deferred, precisely.** *Shipped:* the vocabulary. A\ncompany-operated server is expressible (`kind: fleet`), its account validates on its own\nterms, its route string round-trips to `ZoneFleet`, and `PlacementZone.SelfHosted()`\ngives every downstream consumer one predicate to attribute a token by. Witnessed by\n`TestZonesPartitionTheKnownKinds`, `TestEngineRouteCarriesTheZoneBackOut`,\n`TestShippedExampleRosterCarriesAllThreeZones`, and — at the enforcing floor —\n`TestFleetZoneStaysRemoteAtTheFloor` plus `TestTierOneRouteMirrorAgreesWithTheFloor`.\n*Also shipped:* `Roster.Place` **chooses** a zone (see *Walking the ladder* above).\n*Deferred, and deliberately so:* nothing yet calls it on the live dispatch path, the\nserving path does not yet **record** the zone it used, and the residency floor does not\nyet treat a fleet host as inside a trust boundary. Zone-aware placement, the self-hosted\ntoken-fraction metric, and the escalation ladder are separate, individually reviewable\nchanges on top of this vocabulary — not implied by it. They are tracked as\n[epic #5416](https://github.com/anthony-chaudhary/fak/issues/5416).\n\n### The first consumer: honest stratum attribution\n\n`internal/sessionaudit` classifies audited turns into billing buckets. It used to answer\n\"was this self-hosted?\" from the **model id**, which cannot support the claim: Qwen,\nLlama, Mistral, DeepSeek, GLM and Kimi all publish open weights, so each is servable\neither from hardware you operate or from a vendor's API. That produced two contradictions\nin one file — `deepseek*` was bucketed self-hosted while priced from the published\nDeepSeek *API* rate card, and the roster above binds `qwen/qwen3.6-27b` to **Groq**, a\nvendor, while the classifier called those tokens self-hosted.\n\nA model id now buys only a model-family answer (`open-weights (placement unknown)`), and\nplacement comes from a real signal:\n\n| Call | Answers |\n| --- | --- |\n| `ProviderBucket(model)` | whose *model* it is — never where it ran |\n| `BucketForPlacement(model, zone)` | the bucket, given a `PlacementZone` string |\n| `BucketIsSelfHosted(bucket)` | `(selfHosted, known bool)` — two values on purpose |\n\n`BucketIsSelfHosted` returns a second `known` bool so unattributable volume cannot be\nfolded into either side of a self-hosted fraction. `TestPricedModelsAreNeverBucketedSelfHosted`\nmakes the underlying rule executable and total over the rate card: a model billed per token\nby a third party ran on their hardware, so it can never also be bucketed as self-hosted.\n"}}, "docs/notes/CONCEPT-STUDY-SWE-1-7-2026-07-13.md": {"f1c36fd45ecfb60dc33288bccf3477b30bbd53cf": {"t": 1785747170.4353933, "n": 5977, "h": "0fce05ab07bf1e15", "c": "# Study-repo: SWE-1.7 (Cognition) → fak (2026-07-13)\n\nA `study-repo` pass over **Cognition's SWE-1.7 blog**. SWE-1.7 is a **proprietary, RL-trained coding model** (Kimi K2.7 base, served on Cerebras) — **there is no acquirable repo**, so every borrow is **`inspire`-only**: the source anchor is a blog section plus any named open reference impl (NVIDIA Dynamo, DeepSeek-R1, Kimi K2). No bytes vendored; the license gate is N/A (nothing to copy). The primary blog page is hard-gated behind this environment's fetch policy, so the technique map was assembled from search summaries and cross-checked against the named open references — **re-witness the source text before acting**.\n\nMethod: five technique-axes, one parallel witness agent each (grounded at real fak `path:line`), then an **adversarial verify + dedup** pass per candidate (refute the gap; search existing issues/notes; correct overstatements). Filed under **epic #4597** with the `swe17-inspired` label.\n\n## Decisive finding: the marquee technique is a *deliberate divergence*, not a gap\n\nSWE-1.7's headline **self-compaction** — the model writes its own lossy summary of working state and resumes from that generated text — is **precisely the hazard fak's core context invariant exists to keep off the load-bearing path**. fak's compression is **extractive by construction** (`buildSummary` returns a verbatim source prefix, `internal/contextq/contextq.go:645-705`) with a load-bearing **`FaithfulnessProbe=1.0`** (\"no model in the loop and thus no hallucination surface\", `internal/contextq/viewindex.go:23-29`), and fak chose bounded-context **relays over compaction** (perpetual-sessions epic #1860). The #554 relinking triage (`docs/notes/RESEARCH-relinking-compression-boundary-triage-2026-06-23.md`) records the named invariant: *any model-authored abstractive summarizer on the agentic path must be re-screened at the result-admit boundary, never trusted as a faithful view.* Adopting self-compaction verbatim would regress that. **SWE-1.7 corroborates fak's invariant rather than exposing a gap** — a second external instance (after the #554 triage) of the exact failure mode fak designed around.\n\nThe one residual hunted — *does fak content-witness the **harness's own** generative auto-compaction summary (Claude Code) at the result-admit boundary?* — **dissolved under refutation**: (1) it is a **deliberately-fenced honest limit** (\"fak controls the wire bytes, not the harness's transcript or summarizer; visibility ends at the wire\" — `internal/compactcohere/doc.go`, `INDEX.md:364`, `HARNESS-CACHE-COHERENCE-AUDIT-2026-06-28.md` §2.4/§5, shipped rung-D detector #1134); and (2) a content-address/shingle fingerprint of a sealed span **cannot** match an *abstractive* summary whose content was never present whole in the source — the wrong tool for the real hazard. No leaf.\n\n## Per-axis disposition\n\n| Axis (SWE-1.7 technique) | Verdict | fak seam | Outcome |\n|---|---|---|---|\n| **Self-compaction** (model self-summarizes + resumes) | DIVERGENT | `contextq.go:645`, `viewindex.go:23`, `compactcohere/doc.go`, `INDEX.md:364` | No leaf — see decisive finding. |\n| **Numerical drift** train↔inference (entropy preservation; DeepSeek-R1) | PRESENT | `internal/compute/fp8*`, quantization-specific quality budgets (#4540) | No leaf — fak already bounds quantized-forward error vs a reference with a budget. |\n| **Fault-tolerant in-flight reroute** (Dynamo reroutes trajectories onto a warm peer replica) | PARTIAL — real, **duplicate** | `internal/agent/retry.go:95`, `cmd/fak/guard_child.go:1177`, `guard_rotation.go:110-128` | No new leaf — already tracked by **#3514**; cross-linked with the Dynamo framing. fak already has request-level retry + warm rotate-to-peer-seat reroute; the residual is a bounded warm-relaunch arm for a *transient-wire* child crash. |\n| **Pre-dispatch task-hardening vs reward-hacking** (harden the grader before rollout; DeepSeek-R1 verifiable reward) | PARTIAL — real, **novel** | `internal/issuecontract/contract.go`, `scale.go` | **Filed #4598** — a pre-dispatch **forgeability grade** on a ticket's done-condition (advisory-first `StrictWitness`), the pre-flight complement to fak's post-hoc reward-hack guards (`witness.go` notests, `testintegrity.go` `TEST_CANNOT_FAIL`). |\n| **Efficient reasoning / scope discipline** (reward discourages editing outside the blast radius) | PARTIAL — real, **novel** | `internal/dispatchtick/witness.go:24-47`, `cmd/fak/dispatch_tick_witness.go:167`, `internal/workerworktree/workerworktree.go:406` | **Filed #4599** — witness a worker's **committed footprint against its declared lane** (`CLAIM_OUT_OF_LANE`, advisory); the structural (non-forgeable-witness) restatement of a trained scope reward. |\n\n## What the two filed leaves have in common\n\nBoth restate an SWE-1.7 **trained-reward** discipline as a **fak structural witness**: fak cannot train Claude, so instead of shaping a reward it (a) grades the *contract* before dispatch so a gameable \"done\" fails at triage (#4598), and (b) grades the *committed artifact* after so scope-creep is surfaced as a non-forgeable claim (#4599). Both are **advisory-first**, mirroring `StrictScale`/`CLAIM_TEST_*` fail-open discipline exactly. Adversarial verify sustained both, corrected two overstatements in #4598 (the worker prompt *does* inject generic hardening; `internal/assumecheck` already ships a forgeability-ordered `WitnessKind` to reuse) and one design error in #4599 (a resolving-SHA footprint check is *vacuous* on the worktree path — grade the honest per-worker source instead).\n\n## Related passes\n\nSame genre as the [colibri](docs/notes/CONCEPT-STUDY-COLIBRI-2026-07-11.md), [Dynamo](docs/notes/CONCEPT-STUDY-DYNAMO-2026-07-08.md), and [deepagents](docs/notes/CONCEPT-STUDY-DEEPAGENTS-2026-07-10.md) study-repo passes. Companion skills: `.claude/skills/field-borrow`, `.claude/skills/sota-check`.\n\n_Witness is lexical + a 2026-07-13 snapshot; re-witness before acting._\n"}, "b6af94709b02512a9722424f0a39e7a7cedac1c9": {"t": 1785749570.2273526, "n": 5977, "h": "0fce05ab07bf1e15", "c": "# Study-repo: SWE-1.7 (Cognition) → fak (2026-07-13)\n\nA `study-repo` pass over **Cognition's SWE-1.7 blog**. SWE-1.7 is a **proprietary, RL-trained coding model** (Kimi K2.7 base, served on Cerebras) — **there is no acquirable repo**, so every borrow is **`inspire`-only**: the source anchor is a blog section plus any named open reference impl (NVIDIA Dynamo, DeepSeek-R1, Kimi K2). No bytes vendored; the license gate is N/A (nothing to copy). The primary blog page is hard-gated behind this environment's fetch policy, so the technique map was assembled from search summaries and cross-checked against the named open references — **re-witness the source text before acting**.\n\nMethod: five technique-axes, one parallel witness agent each (grounded at real fak `path:line`), then an **adversarial verify + dedup** pass per candidate (refute the gap; search existing issues/notes; correct overstatements). Filed under **epic #4597** with the `swe17-inspired` label.\n\n## Decisive finding: the marquee technique is a *deliberate divergence*, not a gap\n\nSWE-1.7's headline **self-compaction** — the model writes its own lossy summary of working state and resumes from that generated text — is **precisely the hazard fak's core context invariant exists to keep off the load-bearing path**. fak's compression is **extractive by construction** (`buildSummary` returns a verbatim source prefix, `internal/contextq/contextq.go:645-705`) with a load-bearing **`FaithfulnessProbe=1.0`** (\"no model in the loop and thus no hallucination surface\", `internal/contextq/viewindex.go:23-29`), and fak chose bounded-context **relays over compaction** (perpetual-sessions epic #1860). The #554 relinking triage (`docs/notes/RESEARCH-relinking-compression-boundary-triage-2026-06-23.md`) records the named invariant: *any model-authored abstractive summarizer on the agentic path must be re-screened at the result-admit boundary, never trusted as a faithful view.* Adopting self-compaction verbatim would regress that. **SWE-1.7 corroborates fak's invariant rather than exposing a gap** — a second external instance (after the #554 triage) of the exact failure mode fak designed around.\n\nThe one residual hunted — *does fak content-witness the **harness's own** generative auto-compaction summary (Claude Code) at the result-admit boundary?* — **dissolved under refutation**: (1) it is a **deliberately-fenced honest limit** (\"fak controls the wire bytes, not the harness's transcript or summarizer; visibility ends at the wire\" — `internal/compactcohere/doc.go`, `INDEX.md:364`, `HARNESS-CACHE-COHERENCE-AUDIT-2026-06-28.md` §2.4/§5, shipped rung-D detector #1134); and (2) a content-address/shingle fingerprint of a sealed span **cannot** match an *abstractive* summary whose content was never present whole in the source — the wrong tool for the real hazard. No leaf.\n\n## Per-axis disposition\n\n| Axis (SWE-1.7 technique) | Verdict | fak seam | Outcome |\n|---|---|---|---|\n| **Self-compaction** (model self-summarizes + resumes) | DIVERGENT | `contextq.go:645`, `viewindex.go:23`, `compactcohere/doc.go`, `INDEX.md:364` | No leaf — see decisive finding. |\n| **Numerical drift** train↔inference (entropy preservation; DeepSeek-R1) | PRESENT | `internal/compute/fp8*`, quantization-specific quality budgets (#4540) | No leaf — fak already bounds quantized-forward error vs a reference with a budget. |\n| **Fault-tolerant in-flight reroute** (Dynamo reroutes trajectories onto a warm peer replica) | PARTIAL — real, **duplicate** | `internal/agent/retry.go:95`, `cmd/fak/guard_child.go:1177`, `guard_rotation.go:110-128` | No new leaf — already tracked by **#3514**; cross-linked with the Dynamo framing. fak already has request-level retry + warm rotate-to-peer-seat reroute; the residual is a bounded warm-relaunch arm for a *transient-wire* child crash. |\n| **Pre-dispatch task-hardening vs reward-hacking** (harden the grader before rollout; DeepSeek-R1 verifiable reward) | PARTIAL — real, **novel** | `internal/issuecontract/contract.go`, `scale.go` | **Filed #4598** — a pre-dispatch **forgeability grade** on a ticket's done-condition (advisory-first `StrictWitness`), the pre-flight complement to fak's post-hoc reward-hack guards (`witness.go` notests, `testintegrity.go` `TEST_CANNOT_FAIL`). |\n| **Efficient reasoning / scope discipline** (reward discourages editing outside the blast radius) | PARTIAL — real, **novel** | `internal/dispatchtick/witness.go:24-47`, `cmd/fak/dispatch_tick_witness.go:167`, `internal/workerworktree/workerworktree.go:406` | **Filed #4599** — witness a worker's **committed footprint against its declared lane** (`CLAIM_OUT_OF_LANE`, advisory); the structural (non-forgeable-witness) restatement of a trained scope reward. |\n\n## What the two filed leaves have in common\n\nBoth restate an SWE-1.7 **trained-reward** discipline as a **fak structural witness**: fak cannot train Claude, so instead of shaping a reward it (a) grades the *contract* before dispatch so a gameable \"done\" fails at triage (#4598), and (b) grades the *committed artifact* after so scope-creep is surfaced as a non-forgeable claim (#4599). Both are **advisory-first**, mirroring `StrictScale`/`CLAIM_TEST_*` fail-open discipline exactly. Adversarial verify sustained both, corrected two overstatements in #4598 (the worker prompt *does* inject generic hardening; `internal/assumecheck` already ships a forgeability-ordered `WitnessKind` to reuse) and one design error in #4599 (a resolving-SHA footprint check is *vacuous* on the worktree path — grade the honest per-worker source instead).\n\n## Related passes\n\nSame genre as the [colibri](docs/notes/CONCEPT-STUDY-COLIBRI-2026-07-11.md), [Dynamo](docs/notes/CONCEPT-STUDY-DYNAMO-2026-07-08.md), and [deepagents](docs/notes/CONCEPT-STUDY-DEEPAGENTS-2026-07-10.md) study-repo passes. Companion skills: `.claude/skills/field-borrow`, `.claude/skills/sota-check`.\n\n_Witness is lexical + a 2026-07-13 snapshot; re-witness before acting._\n"}, "13f5f1a6e0391eb28bcbea759890fb009251e5d1": {"t": 1786068093.6472507, "n": 5977, "h": "0fce05ab07bf1e15", "c": "# Study-repo: SWE-1.7 (Cognition) → fak (2026-07-13)\n\nA `study-repo` pass over **Cognition's SWE-1.7 blog**. SWE-1.7 is a **proprietary, RL-trained coding model** (Kimi K2.7 base, served on Cerebras) — **there is no acquirable repo**, so every borrow is **`inspire`-only**: the source anchor is a blog section plus any named open reference impl (NVIDIA Dynamo, DeepSeek-R1, Kimi K2). No bytes vendored; the license gate is N/A (nothing to copy). The primary blog page is hard-gated behind this environment's fetch policy, so the technique map was assembled from search summaries and cross-checked against the named open references — **re-witness the source text before acting**.\n\nMethod: five technique-axes, one parallel witness agent each (grounded at real fak `path:line`), then an **adversarial verify + dedup** pass per candidate (refute the gap; search existing issues/notes; correct overstatements). Filed under **epic #4597** with the `swe17-inspired` label.\n\n## Decisive finding: the marquee technique is a *deliberate divergence*, not a gap\n\nSWE-1.7's headline **self-compaction** — the model writes its own lossy summary of working state and resumes from that generated text — is **precisely the hazard fak's core context invariant exists to keep off the load-bearing path**. fak's compression is **extractive by construction** (`buildSummary` returns a verbatim source prefix, `internal/contextq/contextq.go:645-705`) with a load-bearing **`FaithfulnessProbe=1.0`** (\"no model in the loop and thus no hallucination surface\", `internal/contextq/viewindex.go:23-29`), and fak chose bounded-context **relays over compaction** (perpetual-sessions epic #1860). The #554 relinking triage (`docs/notes/RESEARCH-relinking-compression-boundary-triage-2026-06-23.md`) records the named invariant: *any model-authored abstractive summarizer on the agentic path must be re-screened at the result-admit boundary, never trusted as a faithful view.* Adopting self-compaction verbatim would regress that. **SWE-1.7 corroborates fak's invariant rather than exposing a gap** — a second external instance (after the #554 triage) of the exact failure mode fak designed around.\n\nThe one residual hunted — *does fak content-witness the **harness's own** generative auto-compaction summary (Claude Code) at the result-admit boundary?* — **dissolved under refutation**: (1) it is a **deliberately-fenced honest limit** (\"fak controls the wire bytes, not the harness's transcript or summarizer; visibility ends at the wire\" — `internal/compactcohere/doc.go`, `INDEX.md:364`, `HARNESS-CACHE-COHERENCE-AUDIT-2026-06-28.md` §2.4/§5, shipped rung-D detector #1134); and (2) a content-address/shingle fingerprint of a sealed span **cannot** match an *abstractive* summary whose content was never present whole in the source — the wrong tool for the real hazard. No leaf.\n\n## Per-axis disposition\n\n| Axis (SWE-1.7 technique) | Verdict | fak seam | Outcome |\n|---|---|---|---|\n| **Self-compaction** (model self-summarizes + resumes) | DIVERGENT | `contextq.go:645`, `viewindex.go:23`, `compactcohere/doc.go`, `INDEX.md:364` | No leaf — see decisive finding. |\n| **Numerical drift** train↔inference (entropy preservation; DeepSeek-R1) | PRESENT | `internal/compute/fp8*`, quantization-specific quality budgets (#4540) | No leaf — fak already bounds quantized-forward error vs a reference with a budget. |\n| **Fault-tolerant in-flight reroute** (Dynamo reroutes trajectories onto a warm peer replica) | PARTIAL — real, **duplicate** | `internal/agent/retry.go:95`, `cmd/fak/guard_child.go:1177`, `guard_rotation.go:110-128` | No new leaf — already tracked by **#3514**; cross-linked with the Dynamo framing. fak already has request-level retry + warm rotate-to-peer-seat reroute; the residual is a bounded warm-relaunch arm for a *transient-wire* child crash. |\n| **Pre-dispatch task-hardening vs reward-hacking** (harden the grader before rollout; DeepSeek-R1 verifiable reward) | PARTIAL — real, **novel** | `internal/issuecontract/contract.go`, `scale.go` | **Filed #4598** — a pre-dispatch **forgeability grade** on a ticket's done-condition (advisory-first `StrictWitness`), the pre-flight complement to fak's post-hoc reward-hack guards (`witness.go` notests, `testintegrity.go` `TEST_CANNOT_FAIL`). |\n| **Efficient reasoning / scope discipline** (reward discourages editing outside the blast radius) | PARTIAL — real, **novel** | `internal/dispatchtick/witness.go:24-47`, `cmd/fak/dispatch_tick_witness.go:167`, `internal/workerworktree/workerworktree.go:406` | **Filed #4599** — witness a worker's **committed footprint against its declared lane** (`CLAIM_OUT_OF_LANE`, advisory); the structural (non-forgeable-witness) restatement of a trained scope reward. |\n\n## What the two filed leaves have in common\n\nBoth restate an SWE-1.7 **trained-reward** discipline as a **fak structural witness**: fak cannot train Claude, so instead of shaping a reward it (a) grades the *contract* before dispatch so a gameable \"done\" fails at triage (#4598), and (b) grades the *committed artifact* after so scope-creep is surfaced as a non-forgeable claim (#4599). Both are **advisory-first**, mirroring `StrictScale`/`CLAIM_TEST_*` fail-open discipline exactly. Adversarial verify sustained both, corrected two overstatements in #4598 (the worker prompt *does* inject generic hardening; `internal/assumecheck` already ships a forgeability-ordered `WitnessKind` to reuse) and one design error in #4599 (a resolving-SHA footprint check is *vacuous* on the worktree path — grade the honest per-worker source instead).\n\n## Related passes\n\nSame genre as the [colibri](docs/notes/CONCEPT-STUDY-COLIBRI-2026-07-11.md), [Dynamo](docs/notes/CONCEPT-STUDY-DYNAMO-2026-07-08.md), and [deepagents](docs/notes/CONCEPT-STUDY-DEEPAGENTS-2026-07-10.md) study-repo passes. Companion skills: `.claude/skills/field-borrow`, `.claude/skills/sota-check`.\n\n_Witness is lexical + a 2026-07-13 snapshot; re-witness before acting._\n"}, "153c00a6a313cbd5d94344cce0c7f29b6169b769": {"t": 1786068554.8412213, "n": 5977, "h": "0fce05ab07bf1e15", "c": "# Study-repo: SWE-1.7 (Cognition) → fak (2026-07-13)\n\nA `study-repo` pass over **Cognition's SWE-1.7 blog**. SWE-1.7 is a **proprietary, RL-trained coding model** (Kimi K2.7 base, served on Cerebras) — **there is no acquirable repo**, so every borrow is **`inspire`-only**: the source anchor is a blog section plus any named open reference impl (NVIDIA Dynamo, DeepSeek-R1, Kimi K2). No bytes vendored; the license gate is N/A (nothing to copy). The primary blog page is hard-gated behind this environment's fetch policy, so the technique map was assembled from search summaries and cross-checked against the named open references — **re-witness the source text before acting**.\n\nMethod: five technique-axes, one parallel witness agent each (grounded at real fak `path:line`), then an **adversarial verify + dedup** pass per candidate (refute the gap; search existing issues/notes; correct overstatements). Filed under **epic #4597** with the `swe17-inspired` label.\n\n## Decisive finding: the marquee technique is a *deliberate divergence*, not a gap\n\nSWE-1.7's headline **self-compaction** — the model writes its own lossy summary of working state and resumes from that generated text — is **precisely the hazard fak's core context invariant exists to keep off the load-bearing path**. fak's compression is **extractive by construction** (`buildSummary` returns a verbatim source prefix, `internal/contextq/contextq.go:645-705`) with a load-bearing **`FaithfulnessProbe=1.0`** (\"no model in the loop and thus no hallucination surface\", `internal/contextq/viewindex.go:23-29`), and fak chose bounded-context **relays over compaction** (perpetual-sessions epic #1860). The #554 relinking triage (`docs/notes/RESEARCH-relinking-compression-boundary-triage-2026-06-23.md`) records the named invariant: *any model-authored abstractive summarizer on the agentic path must be re-screened at the result-admit boundary, never trusted as a faithful view.* Adopting self-compaction verbatim would regress that. **SWE-1.7 corroborates fak's invariant rather than exposing a gap** — a second external instance (after the #554 triage) of the exact failure mode fak designed around.\n\nThe one residual hunted — *does fak content-witness the **harness's own** generative auto-compaction summary (Claude Code) at the result-admit boundary?* — **dissolved under refutation**: (1) it is a **deliberately-fenced honest limit** (\"fak controls the wire bytes, not the harness's transcript or summarizer; visibility ends at the wire\" — `internal/compactcohere/doc.go`, `INDEX.md:364`, `HARNESS-CACHE-COHERENCE-AUDIT-2026-06-28.md` §2.4/§5, shipped rung-D detector #1134); and (2) a content-address/shingle fingerprint of a sealed span **cannot** match an *abstractive* summary whose content was never present whole in the source — the wrong tool for the real hazard. No leaf.\n\n## Per-axis disposition\n\n| Axis (SWE-1.7 technique) | Verdict | fak seam | Outcome |\n|---|---|---|---|\n| **Self-compaction** (model self-summarizes + resumes) | DIVERGENT | `contextq.go:645`, `viewindex.go:23`, `compactcohere/doc.go`, `INDEX.md:364` | No leaf — see decisive finding. |\n| **Numerical drift** train↔inference (entropy preservation; DeepSeek-R1) | PRESENT | `internal/compute/fp8*`, quantization-specific quality budgets (#4540) | No leaf — fak already bounds quantized-forward error vs a reference with a budget. |\n| **Fault-tolerant in-flight reroute** (Dynamo reroutes trajectories onto a warm peer replica) | PARTIAL — real, **duplicate** | `internal/agent/retry.go:95`, `cmd/fak/guard_child.go:1177`, `guard_rotation.go:110-128` | No new leaf — already tracked by **#3514**; cross-linked with the Dynamo framing. fak already has request-level retry + warm rotate-to-peer-seat reroute; the residual is a bounded warm-relaunch arm for a *transient-wire* child crash. |\n| **Pre-dispatch task-hardening vs reward-hacking** (harden the grader before rollout; DeepSeek-R1 verifiable reward) | PARTIAL — real, **novel** | `internal/issuecontract/contract.go`, `scale.go` | **Filed #4598** — a pre-dispatch **forgeability grade** on a ticket's done-condition (advisory-first `StrictWitness`), the pre-flight complement to fak's post-hoc reward-hack guards (`witness.go` notests, `testintegrity.go` `TEST_CANNOT_FAIL`). |\n| **Efficient reasoning / scope discipline** (reward discourages editing outside the blast radius) | PARTIAL — real, **novel** | `internal/dispatchtick/witness.go:24-47`, `cmd/fak/dispatch_tick_witness.go:167`, `internal/workerworktree/workerworktree.go:406` | **Filed #4599** — witness a worker's **committed footprint against its declared lane** (`CLAIM_OUT_OF_LANE`, advisory); the structural (non-forgeable-witness) restatement of a trained scope reward. |\n\n## What the two filed leaves have in common\n\nBoth restate an SWE-1.7 **trained-reward** discipline as a **fak structural witness**: fak cannot train Claude, so instead of shaping a reward it (a) grades the *contract* before dispatch so a gameable \"done\" fails at triage (#4598), and (b) grades the *committed artifact* after so scope-creep is surfaced as a non-forgeable claim (#4599). Both are **advisory-first**, mirroring `StrictScale`/`CLAIM_TEST_*` fail-open discipline exactly. Adversarial verify sustained both, corrected two overstatements in #4598 (the worker prompt *does* inject generic hardening; `internal/assumecheck` already ships a forgeability-ordered `WitnessKind` to reuse) and one design error in #4599 (a resolving-SHA footprint check is *vacuous* on the worktree path — grade the honest per-worker source instead).\n\n## Related passes\n\nSame genre as the [colibri](docs/notes/CONCEPT-STUDY-COLIBRI-2026-07-11.md), [Dynamo](docs/notes/CONCEPT-STUDY-DYNAMO-2026-07-08.md), and [deepagents](docs/notes/CONCEPT-STUDY-DEEPAGENTS-2026-07-10.md) study-repo passes. Companion skills: `.claude/skills/field-borrow`, `.claude/skills/sota-check`.\n\n_Witness is lexical + a 2026-07-13 snapshot; re-witness before acting._\n"}}, "internal/agent/deepseek_conformance_test.go": {"c99f9d32ca81593f379f2688d676f9071cefe2df": {"t": 1786068292.3852065, "n": 6236, "h": "bfeb5a78a4a66527", "c": "package agent\n\nimport (\n\t\"context\"\n\t\"strings\"\n\t\"testing\"\n)\n\n// DeepSeek V4 offline conformance fixtures (issue #3011, parent #3006).\n//\n// These pin two documented DeepSeek wire shapes that the OpenAI-compatible\n// adapter path already handles in production but that carried no DeepSeek\n// witness: the provider ERROR ENVELOPE / 429 rate-limit body, and a STREAMING\n// turn whose TERMINAL chunk carries usage (cache hit/miss + reasoning\n// subcounter). DeepSeek rides the same chat-completions adapter as OpenAI, so\n// the whole point of a conformance fixture is to prove fak depends only on the\n// OpenAI-compatible semantics — no DeepSeek-specific branch, no live spend.\n//\n// Everything here is OFFLINE: no DEEPSEEK_API_KEY, no network, no GPU. The\n// error fixtures parse a fixed body through ParseResponse; the streaming\n// fixture replays a fixed SSE body through an httptest server (sseServer).\n\n// TestDeepSeekConformanceErrorEnvelopesSurfaceAsParseErrors proves DeepSeek's\n// OpenAI-shaped `{\"error\":{...}}` envelopes — 429 rate-limit, 402 insufficient\n// balance, 401 auth, 5xx server error — surface as a parse error carrying the\n// provider's own message, never a silently-empty Completion a caller would treat\n// as a real (empty) turn. This is the offline half of \"provider error envelopes\n// / 429 handling\"; the retry/rehome behavior on the live status code is exercised\n// separately by the streaming/status tests.\nfunc TestDeepSeekConformanceErrorEnvelopesSurfaceAsParseErrors(t *testing.T) {\n\tadapter, err := NewTranscriptAdapter(ProviderOpenAI)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tcases := []struct {\n\t\tname string\n\t\traw string\n\t\twantMsg string\n\t}{\n\t\t{\n\t\t\t\"rate_limit_429\",\n\t\t\t`{\"error\":{\"message\":\"Rate limit reached for deepseek-v4-pro. Please retry after a moment.\",\"type\":\"rate_limit_reached\",\"code\":\"429\"}}`,\n\t\t\t\"Rate limit reached\",\n\t\t},\n\t\t{\n\t\t\t\"insufficient_balance_402\",\n\t\t\t`{\"error\":{\"message\":\"Insufficient Balance\",\"type\":\"insufficient_balance\",\"code\":\"insufficient_balance\"}}`,\n\t\t\t\"Insufficient Balance\",\n\t\t},\n\t\t{\n\t\t\t\"authentication_401\",\n\t\t\t`{\"error\":{\"message\":\"Authentication Fails, Your api key is invalid\",\"type\":\"authentication_error\",\"code\":\"invalid_api_key\"}}`,\n\t\t\t\"Authentication Fails\",\n\t\t},\n\t\t{\n\t\t\t\"server_error_503\",\n\t\t\t`{\"error\":{\"message\":\"The server is overloaded or not ready yet.\",\"type\":\"server_error\"}}`,\n\t\t\t\"overloaded\",\n\t\t},\n\t}\n\tfor _, c := range cases {\n\t\tt.Run(c.name, func(t *testing.T) {\n\t\t\tcomp, err := adapter.ParseResponse([]byte(c.raw))\n\t\t\tif err == nil {\n\t\t\t\tt.Fatalf(\"want a parse error for a DeepSeek error envelope, got nil (comp=%+v)\", comp)\n\t\t\t}\n\t\t\tif comp != nil {\n\t\t\t\tt.Fatalf(\"error envelope must not yield a Completion: %+v\", comp)\n\t\t\t}\n\t\t\tif !strings.Contains(err.Error(), c.wantMsg) {\n\t\t\t\tt.Errorf(\"error = %q, want it to carry the provider message %q\", err.Error(), c.wantMsg)\n\t\t\t}\n\t\t})\n\t}\n}\n\n// TestDeepSeekConformanceStreamingTerminalUsage proves a DeepSeek streaming turn\n// with delta.reasoning_content + delta.content, terminated by a usage-only chunk\n// (empty choices) carrying prompt_cache_hit/miss and the reasoning subcounter,\n// assembles correctly: the content is streamed live, the reasoning is accumulated\n// but never lifted into executable tool calls, and the TERMINAL usage surfaces\n// through the same provider-neutral accessors every cache-value consumer reads.\nfunc TestDeepSeekConformanceStreamingTerminalUsage(t *testing.T) {\n\tconst body = \"data: {\\\"model\\\":\\\"deepseek-v4-pro\\\",\\\"choices\\\":[{\\\"delta\\\":{\\\"role\\\":\\\"assistant\\\",\\\"reasoning_content\\\":\\\"Two plus two. \\\"},\\\"finish_reason\\\":null}]}\\n\\n\" +\n\t\t\"data: {\\\"choices\\\":[{\\\"delta\\\":{\\\"reasoning_content\\\":\\\"The answer is four.\\\"},\\\"finish_reason\\\":null}]}\\n\\n\" +\n\t\t\"data: {\\\"choices\\\":[{\\\"delta\\\":{\\\"content\\\":\\\"4\\\"},\\\"finish_reason\\\":null}]}\\n\\n\" +\n\t\t\"data: {\\\"choices\\\":[{\\\"delta\\\":{},\\\"finish_reason\\\":\\\"stop\\\"}]}\\n\\n\" +\n\t\t\"data: {\\\"choices\\\":[],\\\"usage\\\":{\\\"prompt_tokens\\\":1000,\\\"completion_tokens\\\":40,\\\"total_tokens\\\":1040,\\\"prompt_cache_hit_tokens\\\":800,\\\"prompt_cache_miss_tokens\\\":200,\\\"completion_tokens_details\\\":{\\\"reasoning_tokens\\\":25}}}\\n\\n\" +\n\t\t\"data: [DONE]\\n\\n\"\n\tsrv, _ := sseServer(t, body)\n\n\tp := NewHTTPPlanner(srv.URL, \"deepseek-v4-pro\", \"\")\n\tvar got []string\n\tcomp, err := p.CompleteStream(context.Background(), func(frag string) error {\n\t\tgot = append(got, frag)\n\t\treturn nil\n\t}, []Message{{Role: RoleUser, Content: \"2+2?\"}}, nil)\n\tif err != nil {\n\t\tt.Fatalf(\"CompleteStream: %v\", err)\n\t}\n\n\t// Only the final answer reaches the live content sink — reasoning is never streamed.\n\tif strings.Join(got, \"|\") != \"4\" {\n\t\tt.Fatalf(\"sink fragments = %q, want only the final content [4]\", got)\n\t}\n\tif comp.Message.Content != \"4\" {\n\t\tt.Fatalf(\"content = %q, want 4\", comp.Message.Content)\n\t}\n\tif comp.Message.ReasoningContent != \"Two plus two. The answer is four.\" {\n\t\tt.Fatalf(\"reasoning_content = %q, want the accumulated deltas\", comp.Message.ReasoningContent)\n\t}\n\tif len(comp.Message.ToolCalls) != 0 || comp.FinishReason == \"tool_calls\" {\n\t\tt.Fatalf(\"reasoning must not be lifted into tool calls: finish=%q calls=%+v\", comp.FinishReason, comp.Message.ToolCalls)\n\t}\n\tif comp.FinishReason != \"stop\" {\n\t\tt.Fatalf(\"finish = %q, want stop (the terminal usage chunk must not clobber it)\", comp.FinishReason)\n\t}\n\t// The terminal usage chunk normalizes through the provider-neutral accessors:\n\t// 800 cache hit + 200 miss = 1000 resident context, 25 reasoning tokens broken\n\t// out of the 40 completion tokens without deducting from them.\n\tif got := comp.Usage.CachedPromptTokens(); got != 800 {\n\t\tt.Errorf(\"CachedPromptTokens() = %d, want 800 (prompt_cache_hit_tokens)\", got)\n\t}\n\tif got := comp.Usage.UncachedPromptTokens(); got != 200 {\n\t\tt.Errorf(\"UncachedPromptTokens() = %d, want 200 (prompt_cache_miss_tokens)\", got)\n\t}\n\tif got := comp.Usage.ContextWindowTokens(); got != 1000 {\n\t\tt.Errorf(\"ContextWindowTokens() = %d, want 1000 (hit+miss, no double-count)\", got)\n\t}\n\tif got := comp.Usage.ReasoningTokens(); got != 25 {\n\t\tt.Errorf(\"ReasoningTokens() = %d, want 25 (completion_tokens_details.reasoning_tokens)\", got)\n\t}\n\tif comp.Usage.CompletionTokens != 40 {\n\t\tt.Errorf(\"CompletionTokens = %d, want the provider's untouched 40\", comp.Usage.CompletionTokens)\n\t}\n}\n"}}}}