BYO rossoctl cortex (local AuthBridge pipeline with plugins)
Stand up a local rossoctl cortex and run a command (typically claude) behind
an AuthBridge plugin pipeline, so every LLM/tool/agent request the command makes
flows through plugins you choose — for cost tracking, budget enforcement, policy,
token exchange, context compaction, and more.
What "rossoctl cortex" is
A cortex is a local rossoctl backend. Two entry points matter:
rossoctl authbridge exec --config CONFIG -- COMMAND [ARGS...]— the one this skill drives. It hostsCOMMANDbehind the config's plugin pipelines. It starts:- a forward proxy (egress): the command's own outbound HTTP(S) runs the outbound pipeline. This is where LLM traffic to a LiteLLM/Anthropic/OpenAI endpoint is intercepted.
- a reverse proxy (ingress): callers reach the command through it, running the
inbound pipeline. Only started when
listener.rolesincludesreverse. - a TLS bridge: terminates the command's outbound HTTPS so plugins see decrypted requests/responses instead of an opaque CONNECT tunnel. Required for any plugin that must read HTTPS request/response bodies or headers.
- a session API (
:9094by default) exposing recorded traffic for debugging. - It injects
HTTP_PROXY/HTTPS_PROXY(and CA-trust varsNODE_EXTRA_CA_CERTS,REQUESTS_CA_BUNDLE,SSL_CERT_FILEwhen the TLS bridge runs) into the command's environment automatically. Claude Code (Node) honorsHTTPS_PROXY+NODE_EXTRA_CA_CERTS, so no app changes are needed.
rossoctl cortex serve— serves the rossoctl backend API on a local port for a web UI. It does not run a plugin pipeline; it is unrelated to hosting an agent behind plugins. Useauthbridge execfor the plugin pipeline.
Config is YAML with ${ENV_VAR} expansion (undefined ${VARS} are left literal).
See templates/litellm-budget-track.yaml for the canonical shape.
Available plugins
Full details, per-plugin config fields, and direction are in
reference/plugins.md. Summary:
| Plugin | Direction | What it does |
|---|---|---|
litellm-budget-track |
response-reading | Reads the x-litellm-response-cost response header, accumulates daily spend in a JSON ledger file, and returns 429 once a daily budget is exceeded. |
jwt-validation |
inbound | Validate inbound JWT signature / issuer / audience against JWKS. |
token-exchange |
outbound | RFC 8693 token exchange per host route (Keycloak/Entra/Okta). |
static-inject |
outbound | Swap a placeholder credential for a real static secret on egress. |
ibac |
outbound | LLM-judge intent-based access control on outbound tool calls. |
opa |
both | OPA policy bundles on inbound + outbound requests. |
sparc |
outbound | Pre-tool reflection; blocks ungrounded/hallucinated tool calls. |
session-budget |
outbound | Per-session token/call/duration budgets via Redis. |
token-broker |
outbound | Exchange tokens via an external broker per route. |
a2a-parser / mcp-parser / inference-parser |
parsers | Parse A2A / MCP / OpenAI-inference traffic into pctx.Extensions.* for downstream guardrails. |
context-guru |
outbound | Compact the outbound LLM request context (opt-in build tag). |
cpex |
outbound | APL DSL + named CPEX policy plugins (separate authbridge-cpex binary). |
To place a plugin in the pipeline, add it under pipeline.outbound.plugins (for
egress) or pipeline.inbound.plugins (for ingress), each with an optional
config: block. In authbridge exec the forward proxy runs the outbound
pipeline, so plugins acting on the command's own LLM/tool egress — including
litellm-budget-track — go under pipeline.outbound.
Procedure
Step 1 — Get the rossoctl binary
This skill does not assume any repo is already checked out. If rossoctl is not
already on PATH, clone and build it. It is pure Go (build with CGO_ENABLED=0).
# Already installed? Use it.
command -v rossoctl && rossoctl version
For working litellm-budget-track cost tracking you need the fixed cortex
branch (not yet in a tagged authlib), so clone rossoctl-cli and cortex as
siblings in one scratch dir and build rossoctl against the local cortex with a
replace directive — no manual patching. The fix_streaming_litellm_plugin branch is
stacked on fix_litellm_plugin, so it has the response-header fix (+ -original
fallback) and streaming (SSE) cost tracking for Claude Code's /v1/messages:
mkdir -p "$HOME/rossoctl-src"
cd "$HOME/rossoctl-src" # scratch dir; anywhere writable
git clone https://github.com/rossoctl/rossoctl-cli.git
git clone -b fix_streaming_litellm_plugin https://github.com/aslom/cortex.git
# branch: https://github.com/aslom/cortex/tree/fix_streaming_litellm_plugin
# (header fix + -original fallback + streaming usage pricing).
# For the header fix only (no streaming) use -b fix_litellm_plugin.
cd rossoctl-cli
# replace path is relative to rossoctl-cli's go.mod, i.e. the sibling cortex clone:
go mod edit -replace github.com/rossoctl/cortex/authbridge/authlib=../cortex/authbridge/authlib
GOFLAGS=-mod=mod go mod tidy
CGO_ENABLED=0 go build -o bin/rossoctl .
export PATH="$PWD/bin:$PATH" # or: sudo mv bin/rossoctl /usr/local/bin/
rossoctl version
If you don't need cost tracking (or once the fix lands in an upstream
rossoctl/cortex release), a plain build pulls the authlib automatically — no cortex
clone or replace needed:
git clone https://github.com/rossoctl/rossoctl-cli.git && cd rossoctl-cli
CGO_ENABLED=0 go build -o bin/rossoctl . && export PATH="$PWD/bin:$PATH"
Or install a prebuilt release: curl -fsSL https://raw.githubusercontent.com/rossoctl/rossoctl-cli/main/downloadRossoctl | sh
then export PATH="$PATH:$HOME/.config/rossoctl".
Step 2 — Ask the user what they want
Use AskUserQuestion to choose:
- Which plugins to enable (multi-select from the table above; default
litellm-budget-track). - The command to host (default
claude). - For
litellm-budget-track: the daily budget (USD) and whether they want per-agent isolation (separate ledger per agent via an env var — recommended).
Skip questions whose answers are already obvious from the request.
Step 3 — Generate the config
Copy templates/litellm-budget-track.yaml into a working dir (e.g.
./.cortex/CONFIG.yaml) and adjust:
listener.roles:[forward]for egress-only (LLM cost tracking); addreverseonly if you are fronting an inbound service.tls_bridge: keepmode: enabled,generate_ca: true.ca_diris${CORTEX_CA_DIR}in the template — export it to an absolute, persistent, sandbox-writable path (e.g.CORTEX_CA_DIR="$PWD/.cortex/tls-bridge-ca"); it must survive restarts, else clients re-trust a new CA each run, and/tmpmay be blocked. Defaultports: [443, 8443]cover HTTPS LLM endpoints.pipeline.outbound.plugins: the chosen plugins. Forlitellm-budget-track:- name: litellm-budget-track config: spend_file: "${CORTEX_SPEND_FILE}" # per-agent ledger, from env max_budget: ${CORTEX_MAX_BUDGET} # daily budget in USD # REQUIRED for Claude Code and any stream:true client: streamed # /v1/messages responses report cost 0 in the header, so without these # per-token rates a streamed call records $0. Omit only for non-streaming # (curl / OpenAI /v1/chat/completions), which is priced from the header. input_cost_per_token: 0.000003 # example: $3 / 1M UNCACHED input tokens output_cost_per_token: 0.000015 # example: $15 / 1M output tokens # Prompt-cache tiers — set to your provider's real prices (see caveat 6); # if omitted they default to input_cost_per_token (flat), overstating # cache-heavy Claude Code traffic up to ~10× and tripping the 429 early. cache_write_cost_per_token: 0.00000375 # example: $3.75 / 1M (write premium) cache_read_cost_per_token: 0.0000003 # example: $0.30 / 1M (read discount)
Step 4 — Run the agent (per-agent isolation via env var)
Each agent gets its own CORTEX_SPEND_FILE, so usage is tracked separately even
though every agent shares the same LiteLLM credentials
(ANTHROPIC_BASE_URL / ANTHROPIC_AUTH_TOKEN). Because claude streams, the config
must carry input_cost_per_token / output_cost_per_token (Step 3) or the ledger
stays $0 — see caveat 1.
export CORTEX_CA_DIR="$PWD/.cortex/tls-bridge-ca" # persistent TLS-bridge CA (shared)
# Agent "alpha"
CORTEX_SPEND_FILE="$PWD/.cortex/spend-alpha.json" CORTEX_MAX_BUDGET=5.00 \
rossoctl authbridge exec --config ./.cortex/CONFIG.yaml \
--instanceName alpha --sessionServer "" -- claude -p "…"
# Agent "beta" — same credentials, separate ledger
CORTEX_SPEND_FILE="$PWD/.cortex/spend-beta.json" CORTEX_MAX_BUDGET=2.00 \
rossoctl authbridge exec --config ./.cortex/CONFIG.yaml \
--instanceName beta --sessionServer "" -- claude -p "…"
The ledger (spend-<agent>.json) accumulates {date,total_spend,total_calls} and
resets at midnight UTC. When total_spend >= max_budget, further requests get
HTTP 429 (budget.exceeded).
Step 5 — Verify
- Watch the pipeline log:
tail -f /tmp/authbridge.log(or the--logfileyou set;LOG_LEVEL=debug rossoctl -v authbridge exec …shows per-plugin invocations). - After a run, inspect each ledger:
cat ./.cortex/spend-*.json. - Confirm the numbers differ per agent and match
calls × per-call cost.
Critical caveats (learned by testing this end-to-end)
Endpoint header naming.
litellm-budget-trackreads the barex-litellm-response-costheader. On LiteLLM ≥ ~1.85, the OpenAI/v1/chat/completionsresponses carry it, but the Anthropic/v1/messagesendpoint (what Claude Code uses) may only emitx-litellm-response-cost-original(plus-discount-amount/-margin-*). If Claude Code traffic records$0, check the actual response headers with a direct curl:curl -sS -D - -o /dev/null -H "authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \ -H "content-type: application/json" \ -d '{"model":"<a-model-the-key-can-access>","max_tokens":16,"messages":[{"role":"user","content":"hi"}]}' \ "$ANTHROPIC_BASE_URL/v1/chat/completions" | grep -i litellmVerify per-agent tracking against an endpoint that emits the bare header. Also note that streamed responses (Claude Code's
/v1/messages, or anystream:truecall) report cost0in the header — the total is not known when the headers are sent. To track those, use the streaming-enhanced plugin (fix_streaming_litellm_plugin) and setinput_cost_per_token/output_cost_per_tokenin the config (see the template) so cost is computed from the parsed token usage.Plugin bug (fix lives on a branch, not yet upstream). Older
litellm-budget-trackreadspctx.Headers(the request headers) inOnResponseinstead ofpctx.ResponseHeaders, so it never records cost. If your build shows$0despite the cost header being present, you built against an unfixedcortex. Use the ready-made branchaslom/cortex@fix_streaming_litellm_pluginas shown in Step 1 (git clone -b fix_streaming_litellm_plugin …). It is stacked onfix_litellm_plugin, so it has the response-header fix (pctx.ResponseHeaders.Get(...), mirroring theopaplugin), the-originalfallback, and streaming usage-based pricing — plus unit tests. No manual patching required. (For the header fix alone,fix_litellm_pluginsuffices.)TLS bridge is mandatory for HTTPS cost tracking — without it, egress is an opaque CONNECT tunnel and the plugin can't read response headers.
ca_dirmust persist across runs and be writable; regenerating the CA each run breaks client trust.Sandbox paths — write configs, ledgers, CA dir, and logs under the project tree, not
/tmp, if the environment restricts/tmp.Prompt caching (streaming pricing only). Claude Code caches heavily, and a provider charges a premium to write a cache entry and a steep discount to read one — so two turns with identical prompt-token counts can differ ~10× in price. The usage fallback prices the tiers separately:
input_cost_per_token(uncached),cache_write_cost_per_token,cache_read_cost_per_token. If you leave the two cache rates unset they default toinput_cost_per_token(flat), which overstates cache-heavy traffic up to ~10× and trips the 429 that much earlier — set them to your provider's real cache prices. This only affects the usage-fallback (streamed) path; when LiteLLM'sx-litellm-response-costheader is present it already accounts for cache tiers and wins.
Assets
templates/litellm-budget-track.yaml— ready-to-edit forward-proxy + TLS-bridge +litellm-budget-trackconfig.templates/agent.sh— a minimal "agent" that makes N LiteLLM/v1/chat/completionscalls through the injected proxy; handy for validating tracking without a full app.reference/plugins.md— full plugin catalog with per-plugin config fields.