Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ prefix is sugar for the common cases:
|---------------------------------|---------------------------------------------------------|
| `policy_ir` term in the body | run that Σ_pol policy (the primary path) |
| `""` / unprefixed `model` | the `default` policy |
| `model = "profile:NAME"` | a named profile from the catalog (only `default` ships) |
| `model = "profile:NAME"` | a named profile from the catalog (`default` and `agent` ship) |
| `model = "family:FAMILY"` | default, pinned to a model family |
| `model = "pin:PROVIDER/FAMILY"` | default, pinned to one (provider, family) |

Expand Down Expand Up @@ -135,14 +135,21 @@ curl -s http://127.0.0.1:8080/x/policy/templates/cheapest-family \
}'
```

The three templates are:
The four templates are:

- `cheapest-family` — stay in one exact family and minimize expected token
cost; `provider_strategy: "ordered"` instead enforces Codex → AntSeed →
Bedrock → OpenRouter, skipping unavailable providers and retaining
breaker-open routes only as final fallbacks.
- `smart-value` — minimize cost among the current top five intelligence models
(the shortlist size and price/reliability rails are configurable).
- `agent` — the reusable `profile:agent` policy for autonomous tool users:
require tools, 128k context, top-ten measured intelligence and reliable,
priced routes; prefer healthy Codex/direct providers before gateways and
trusted AntSeed peers; cap the cascade at eight candidates. The policy also
sets a 10s first-token and 22s per-attempt timeout and moves immediately to a
different candidate on provider failures, so a stalled first route cannot
consume the complete request deadline.
Comment on lines +146 to +152

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

State the agent price ceilings in this bullet.

The paragraph below this list says all templates default to $5 input / $25 output per-million-token ceilings. The agent template uses $15 input and $30 output (AGENT_MAX_PRICE_IN, AGENT_MAX_PRICE_OUT in policy_templates.py). Add the actual rails here so the document stays accurate.

📝 Proposed fix
 - `agent` — the reusable `profile:agent` policy for autonomous tool users:
   require tools, 128k context, top-ten measured intelligence and reliable,
-  priced routes; prefer healthy Codex/direct providers before gateways and
+  priced routes under $15 input / $30 output per million tokens; prefer
+  healthy Codex/direct providers before gateways and
   trusted AntSeed peers; cap the cascade at eight candidates. The policy also
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- `agent` — the reusable `profile:agent` policy for autonomous tool users:
require tools, 128k context, top-ten measured intelligence and reliable,
priced routes; prefer healthy Codex/direct providers before gateways and
trusted AntSeed peers; cap the cascade at eight candidates. The policy also
sets a 10s first-token and 22s per-attempt timeout and moves immediately to a
different candidate on provider failures, so a stalled first route cannot
consume the complete request deadline.
- `agent` — the reusable `profile:agent` policy for autonomous tool users:
require tools, 128k context, top-ten measured intelligence and reliable,
priced routes under $15 input / $30 output per million tokens; prefer
healthy Codex/direct providers before gateways and
trusted AntSeed peers; cap the cascade at eight candidates. The policy also
sets a 10s first-token and 22s per-attempt timeout and moves immediately to a
different candidate on provider failures, so a stalled first route cannot
consume the complete request deadline.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 146 - 152, Update the README `agent` template bullet
to state its actual $15 input and $30 output per-million-token price ceilings,
matching `AGENT_MAX_PRICE_IN` and `AGENT_MAX_PRICE_OUT` in
`policy_templates.py`.

- `default` — the actual policy used by OpenAI-compatible callers that send no
policy: prefer a top-five intelligence model, then enforce Codex → AntSeed →
Bedrock → OpenRouter and use a 75% cost / 25% intelligence value score inside
Expand Down
6 changes: 5 additions & 1 deletion SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,14 @@ Every step is a request with the **same** `Authorization: Bearer <key>` you used
lists the blessed choices. Compile one with
`POST /x/policy/templates/{id}` (for example
`{"family":"glm-5.2","provider_strategy":"ordered"}` against
`cheapest-family`). Author raw `policy_ir` only when the templates cannot
`cheapest-family`, or `agent` for a tool-running autonomous client). Author
raw `policy_ir` only when the templates cannot
express the intent. The published `default` template is byte-for-byte
equivalent after normalization to the policy used when an
OpenAI-compatible request sends no `policy_ir`.
The published `agent` template is likewise identical to `profile:agent` and
owns its first-token/per-attempt budgets, trust gate, and fast-fallback plan;
callers should select the profile instead of copying its raw term.
2. **Admit & identify — no spend.** `POST /x/policy/normalize` `{policy_ir}` → `{policy_ir, fingerprint, version}`. A `400` here pinpoints what's invalid (unknown op, undeclared field, …) so you fix the term before paying.
3. **Preview the ranking — no spend.** `POST /x/rank` `{policy_ir}` → `{ranked, rejected}`: the candidates this host would admit and how it orders them, plus the ones it filtered out, each with the `reason` it failed. This is how you see *what your policy does* without a single call.
4. **Run it for real.** `POST /v1/chat/completions` with `policy_ir` (or `flow_ir`) + `messages` (the example above). A real call — real spend.
Expand Down
26 changes: 23 additions & 3 deletions auth_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@
UPSTREAM = os.getenv("ROUTER_UPSTREAM", "http://router:18080").rstrip("/")
CALLER_KEYS_JSON = os.getenv("CALLER_KEYS_JSON", "{}")
CALLER_KEYS_SHA256_JSON = os.getenv("CALLER_KEYS_SHA256_JSON", "{}")
# GitOps/bootstrap keys arrive from a reconciled workload Secret. Keep them in
# a separate channel because load_env_secrets() intentionally lets the
# dashboard-managed PVC override CALLER_KEYS_JSON; replacing the whole map
# would otherwise make a reconciled workload key disappear after the first
# dashboard-issued key is persisted.
CALLER_KEYS_BOOTSTRAP_JSON = os.getenv("CALLER_KEYS_BOOTSTRAP_JSON", "{}")
RATE_PER_MIN = int(os.getenv("RATE_PER_MIN", "600"))
BURST = int(os.getenv("BURST", "200"))
RECENT_LIMIT = int(os.getenv("DASHBOARD_RECENT_LIMIT", "200"))
Expand Down Expand Up @@ -79,8 +85,21 @@ def _load_caller_map(raw: str, name: str) -> Dict[str, str]:
raise RuntimeError(f"invalid {name}: {exc}") from exc


CALLER_KEYS: Dict[str, str] = _load_caller_map(CALLER_KEYS_JSON, "CALLER_KEYS_JSON")
CALLER_KEY_HASHES: Dict[str, str] = _load_caller_map(CALLER_KEYS_SHA256_JSON, "CALLER_KEYS_SHA256_JSON")
def _bootstrap_caller_key_hashes(raw: str) -> Dict[str, str]:
"""Load GitOps keys as hashes so the dashboard can never reveal them."""
return {
hashlib.sha256(token.encode()).hexdigest(): owner
for token, owner in _load_caller_map(
raw, "CALLER_KEYS_BOOTSTRAP_JSON").items()
}


CALLER_KEYS: Dict[str, str] = _load_caller_map(
CALLER_KEYS_JSON, "CALLER_KEYS_JSON")
CALLER_KEY_HASHES: Dict[str, str] = _bootstrap_caller_key_hashes(
CALLER_KEYS_BOOTSTRAP_JSON)
CALLER_KEY_HASHES.update(_load_caller_map(
CALLER_KEYS_SHA256_JSON, "CALLER_KEYS_SHA256_JSON"))

logging.basicConfig(level=os.getenv("LOG_LEVEL", "INFO"), format="%(message)s")
log = logging.getLogger("llm-router-auth-proxy")
Expand Down Expand Up @@ -4940,6 +4959,7 @@ def _dashboard_html() -> str:
<select id='bTemplate' class='select' style='width:auto'>
<option value='cheapest-family'>Cheapest in one family</option>
<option value='smart-value'>Smart value</option>
<option value='agent'>Stable tool agent</option>
<option value='default'>Default for vanilla clients</option>
</select>
<input id='bTemplateFamily' class='input' list='familyOptions' placeholder='model family, e.g. glm-5.2' style='min-width:220px'>
Expand Down Expand Up @@ -5215,7 +5235,7 @@ def _dashboard_html() -> str:
async function bTest(){$('bError').style.display='none';$('bTestResult').innerHTML='<div class="muted small" style="margin-top:8px">Running…</div>';try{const term=bCurrentTerm();const prompt=$('bTestPrompt').value.trim()||'Reply exactly: pong';const r=await fetch('/dashboard/api/policy/test',{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json'},body:JSON.stringify({policy_ir:term,prompt})});if(r.status===401){showLogin();return}const d=await r.json();if(!r.ok)throw new Error(d.error?.message||(typeof d.error==='string'?d.error:'test '+r.status));const okc=d.ok?'':'bad';const out=d.text?`<pre class="mono small" style="white-space:pre-wrap;margin-top:8px;background:rgba(255,255,255,.02);border:1px solid var(--line);border-radius:8px;padding:8px">${esc(d.text)}</pre>`:`<div class="bad small" style="margin-top:8px">${esc(d.error||'no output')}</div>`;$('bTestResult').innerHTML=`<div class="actMeta" style="margin-top:8px"><span class="pill ${okc}">status ${esc(d.status)}</span><span class="pill">${esc(d.provider||'—')}${d.served_model_id?' · '+esc(d.served_model_id):''}</span></div><div class="label small" style="margin-top:12px">How it routed — executed live, real spend</div>${actDetail(d)}<div class="label small" style="margin-top:12px">Answer</div>${out}<div class="muted small" style="margin-top:6px">Also recorded in <b>Activity</b>.</div>`;toast('Test call done')}catch(e){bFail(e.message)}}
async function loadBuilderFamilies(){try{const r=await fetch('/dashboard/api/policies',{credentials:'same-origin'});if(!r.ok)return;const d=await r.json();const fams=new Set();(d.profiles||[]).forEach(p=>(p.models||[]).forEach(m=>{if(m.name)fams.add(m.name)}));$('familyOptions').innerHTML=[...fams].sort().map(f=>`<option value="${esc(f)}">`).join('')}catch(e){}}
async function loadBuilderFields(){try{const r=await fetch('/dashboard/api/fields',{credentials:'same-origin'});if(!r.ok)return;const d=await r.json();const num=[],bool=[];(d.fields||[]).forEach(f=>{const e={name:f.name,group:f.group||'model'};if(f.sort==='Bool')bool.push(e);else if(f.sort==='Num')num.push(e)});if(num.length)bFields.num=num;bFields.bool=bool;if(activeTab==='builder'&&!bRawMode)bRender()}catch(e){}}
function bTemplateChanged(){const id=$('bTemplate').value,family=id==='cheapest-family';$('bTemplateFamily').style.display=family?'':'none';$('bTemplateStrategy').style.display=family?'':'none';$('bTemplateHelp').textContent=family?'Stays inside the exact family; unavailable providers are skipped.':id==='default'?'The actual no-policy default: top-five first, then Codex → AntSeed → Bedrock → OpenRouter.':'Chooses the cheapest reliable route inside the top-five intelligence shortlist.'}
function bTemplateChanged(){const id=$('bTemplate').value,family=id==='cheapest-family';$('bTemplateFamily').style.display=family?'':'none';$('bTemplateStrategy').style.display=family?'':'none';$('bTemplateHelp').textContent=family?'Stays inside the exact family; unavailable providers are skipped.':id==='agent'?'Quality-first tool routing with trusted peers and attempt budgets that leave room for failover.':id==='default'?'The actual no-policy default: top-five first, then Codex → AntSeed → Bedrock → OpenRouter.':'Chooses the cheapest reliable route inside the top-five intelligence shortlist.'}
async function bCreateTemplate(){$('bError').style.display='none';const id=$('bTemplate').value,opts={};if(id==='cheapest-family'){const family=$('bTemplateFamily').value.trim();if(!family){bFail('Choose a model family first.');$('bTemplateFamily').focus();return}opts.family=family;opts.provider_strategy=$('bTemplateStrategy').value}const btn=$('bLoadTemplate'),old=btn.textContent;btn.disabled=true;btn.textContent='Creating…';try{const r=await fetch('/dashboard/api/policy/templates/'+encodeURIComponent(id),{method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json'},body:JSON.stringify(opts)});if(r.status===401){showLogin();return}const d=await r.json();if(!r.ok)throw new Error(d.error?.message||`template ${r.status}`);bSetMode('raw');$('bRawTerm').value=JSON.stringify(d.policy_ir,null,2);lastBuiltPolicy=d;await bReview();toast('Template created and previewed')}catch(e){bFail(e.message)}finally{btn.disabled=false;btn.textContent=old}}
// One-click teaching policies, authored as structured state (filters/scores/pick).
const B_EXAMPLES={ex1:{filters:[{field:'in_top_k',k:'5',by:'bench_intelligence'},{field:'in_top_k',k:'5',by:'bench_coding'}],scores:[{field:'field:price_in',w:'1',norm:true,inv:true}],gate:false,selector:'argmax',topn:''},ex2:{filters:[],scores:[{field:'field:bench_intelligence',w:'1',norm:true,inv:false},{field:'field:bench_coding',w:'1',norm:true,inv:false},{field:'field:bench_agentic',w:'1',norm:true,inv:false}],gate:false,selector:'argmax',topn:'3'}};
Expand Down
119 changes: 119 additions & 0 deletions config.live.lua
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,34 @@ local DEFAULT_QUALITY_TOP_N = 5
local DEFAULT_COST_WEIGHT = 0.75
local DEFAULT_INTELLIGENCE_WEIGHT = 0.25

-- Quality-first defaults for long-running tool agents. Unlike the vanilla
-- chat policy, each provider attempt is bounded inside the policy itself so a
-- slow first route cannot consume the router's complete fallback deadline.
local AGENT_PROVIDER_ORDER = {
{ "openai_codex" },
{ "openai", "anthropic", "gemini", "bedrock", "bedrock_market" },
{ "openrouter", "openrouter_market" },
{ "antseed" },
}
local AGENT_RELIABILITY_FLOOR = 0.8
local AGENT_MIN_CONTEXT = 128000
local AGENT_MAX_PRICE_IN = 15.0
local AGENT_MAX_PRICE_OUT = 30.0
local AGENT_QUALITY_TOP_N = 10
local AGENT_TOP_K = 8
local AGENT_INTELLIGENCE_WEIGHT = 0.60
local AGENT_INPUT_COST_WEIGHT = 0.15
local AGENT_RELIABILITY_WEIGHT = 0.25
local AGENT_FIRST_TOKEN_TIMEOUT_MS = 10000
local AGENT_ATTEMPT_TIMEOUT_MS = 22000
local TRUSTED_ANTSEED_PEERS = {
"4668854ba3e8b094e6f48fbeb59cec1cfde162f2", -- Dark Signal
"9e8f9aaee684298b7f2af2ae008e3692f0e9f4f7", -- Venice.ai Proxy
"1d90f467689d499dc435e5744b4613c3203eb0aa", -- Open Forge
"ded67f398fcf7b7884ff7c669d9a4fe820d7657c", -- Chutes
"6ec1c8189340370220ea253612f23f6dfe9f5b75", -- The Seeder
}

local BALANCED_RETRY = {
rate_limit = { action = "next_candidate", open_breaker_ms = 30000 },
timeout = { action = "next_candidate" },
Expand All @@ -67,6 +95,25 @@ local BALANCED_RETRY = {
unknown = { action = "next_candidate" },
}

-- Retry a different route, not the same slow route. With a 50 s outer request
-- deadline this is what makes the ranked agent cascade operational rather than
-- decorative.
local AGENT_RETRY = {
rate_limit = { action = "next_candidate", open_breaker_ms = 30000 },
timeout = { action = "next_candidate" },
server_error = { action = "next_candidate" },
auth_error = { action = "disable_provider" },
bad_request = { action = "next_candidate" },
content_filter = { action = "next_candidate" },
bad_response = { action = "next_candidate" },
model_unavailable = { action = "next_provider_same_model", mark_unavailable_ms = 300000 },
network_error = { action = "next_candidate" },
context_overflow = { action = "next_candidate" },
stream_interrupted = { action = "abort" },
payment_required = { action = "next_candidate", open_breaker_ms = 300000 },
unknown = { action = "next_candidate" },
}

local function fail_plan(actions)
local keys = {}
for reason, _ in pairs(actions) do
Expand Down Expand Up @@ -146,6 +193,68 @@ local function default_policy_ir()
}
end

local function trusted_antseed_gate()
local gate = {
"or",
{ "not", { "provider_eq", "antseed" } },
{ "cmp", "reputation_score", "gt", 95 },
}
for _, peer in ipairs(TRUSTED_ANTSEED_PEERS) do
gate[#gate + 1] = { "served_by_eq", peer }
end
return gate
end

local function agent_policy_ir()
local provider_preds = {}
for _, group in ipairs(AGENT_PROVIDER_ORDER) do
provider_preds[#provider_preds + 1] = provider_pred(group)
end

local provider_selector = { "argmax" }
for i = #provider_preds, 1, -1 do
provider_selector = { "prefer", provider_preds[i], provider_selector }
end
local selector = {
"top_k",
AGENT_TOP_K,
{
"prefer",
{ "not", { "is", "breaker_open" } },
provider_selector,
},
}

return {
"policy",
{ "and",
{ "meets_req" },
{ "not", { "is", "disabled" } },
{ "is", "cap_tools" },
{ "cmp", "context", "ge", AGENT_MIN_CONTEXT },
{ "cmp", "success_rate", "ge", AGENT_RELIABILITY_FLOOR },
{ "cmp", "bench_intelligence_rank", "le", AGENT_QUALITY_TOP_N },
{ "cmp", "price_in", "le", AGENT_MAX_PRICE_IN },
{ "cmp", "price_out", "le", AGENT_MAX_PRICE_OUT },
trusted_antseed_gate(),
},
{ "add",
{ "scale", AGENT_INTELLIGENCE_WEIGHT,
{ "normalize", { "field", "bench_intelligence" } } },
{ "scale", AGENT_INPUT_COST_WEIGHT,
{ "neg", { "normalize", { "field", "price_in" } } } },
{ "scale", AGENT_RELIABILITY_WEIGHT,
{ "field", "success_rate" } },
},
selector,
{ "seq",
{ "set_param", "first_token_timeout_ms", AGENT_FIRST_TOKEN_TIMEOUT_MS },
{ "set_param", "timeout_ms", AGENT_ATTEMPT_TIMEOUT_MS },
},
fail_plan(AGENT_RETRY),
}
end

return {

providers = {
Expand Down Expand Up @@ -610,6 +719,15 @@ return {
},

profiles = {
-- Reusable default for autonomous/tool agents. The profile owns its
-- attempt budgets and fast-fallback plan, so callers only need to send
-- model="profile:agent" instead of copying a policy term and timeout
-- knobs into every deployment.
agent = {
policy_ir = agent_policy_ir(),
selector = "top_k",
retry_policy = "agent",
},
-- Callers may send their own policy_ir. Plain OpenAI-compatible
-- requests use this identified policy: healthy Codex -> AntSeed ->
-- Bedrock -> OpenRouter, with safe price/reliability rails and the best
Expand All @@ -623,6 +741,7 @@ return {

retry_policies = {
balanced = BALANCED_RETRY,
agent = AGENT_RETRY,
},

-- Model-level observation fields (registered traits from OpenRouter, read
Expand Down
5 changes: 5 additions & 0 deletions docs/DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ Routing model:
- `router` is not published; only the `ingress` proxy can reach it on the Compose network.
- `ingress` binds `127.0.0.1:${LLM_ROUTER_HOST_PORT:-8080}` only. Put a private LB, Tailscale, service mesh, or Caddy internal-only route in front if other hosts need access.
- The ingress checks the OpenAI-SDK bearer token against `CALLER_KEYS_JSON`; each token maps to a caller name for audit logs.
- GitOps-managed workload tokens should use `CALLER_KEYS_BOOTSTRAP_JSON`. It is
hashed in memory at boot and merged into the hash-only key map, so it is not
revealable through the dashboard and a later dashboard write cannot replace
the reconciled map. Keep the raw JSON in a Secret, never in a Deployment
manifest.
- Provider secrets and OAuth files are read only by the router/sidecars from env/secrets or host mounts. Clients never receive provider keys.
- Logs are JSON lines with caller, route, status, latency, and router-chosen provider/model when available.

Expand Down
Loading
Loading