Agent workflows · 2026-08-29
HarnessRouter LLMTR provider: one key, three harnesses
The llmtr row in HarnessRouter feeds Claude Code, Codex and Hermes from one base URL and one key. Wire shapes, the model intersection table and the four exclusion rules.
What the llmtr row solves in harnessrouter
harnessrouter feeds three separate coding harnesses from a single console: Claude Code, Codex and Hermes. Even when they call the same model, these three do not speak the same wire. One expects an Anthropic message envelope, one produces an OpenAI Responses body, one sends a classic chat body. The router's job is to know which harness can actually work with a provider you connect, and to offer you only the combinations that work.
The feat/llmtr-provider branch in the knowhycodata/harnessrouter repository adds one row to the provider table in the README, and that row is ticked for all three harnesses. In practice: you define a single LLMTR connection in the console and all three harnesses can use it. No separate account, key or base URL per harness.
This guide is not about installation steps. It is about the two decisions behind that row: which wire shapes the connection is wired to, and why the model list is deliberately shortened.
The connection record: a provider that asks for no extra field
The LLMTR entry in the router's integrations table carries four things: display name, base URL, the kind of secret to store and a key format hint. The field list is empty. No region, project id, account id or deployment name is requested, because the endpoint has no equivalent for them. That is the most visible difference from the cloud provider rows next to it.
The key hint shows the llmtr prefix. The console uses that hint only to sanity-check the shape of what you paste; the verification request carries the key verbatim in the Authorization header.
Fields the LLMTR entry carries in the harnessrouter integrations table
label: LLMTR
base_url: https://llmtr.com/v1
fields: [] # no region, project or account id is asked for
secret: api_key
secret_label: API Key
key_hint: "llmtr-..."
Three harnesses, three wire shapes, one base URL
Inside the router each provider and harness pair maps to a connection shape. For LLMTR the Claude Code and Codex pairs map to the tokenrouter shape, and the Hermes pair maps to the openai-api shape. Because one base URL answers on two different body shapes, the row is wired the same way as another multi-surface provider that behaves identically.
The interesting note is the source file's own: LLMTR's Anthropic surface is not separately advertised in the published catalogue, where every model lists OpenAI endpoints. The surface is nonetheless real. The branch author probed it live on 19 August 2026: a POST to /v1/messages with anthropic/claude-sonnet-4.6 returned 200 and a proper Anthropic message envelope, while a path that does not exist under /v1 answered with a structured not_found. The surface was read from a call, not from a catalogue.
The table below shows where each harness sends its request over the same connection, and which model completed a real agentic turn that day.
| Harness | Connection shape | Endpoint the request reaches | Model that completed the turn |
|---|---|---|---|
| Claude Code | tokenrouter | /v1/messages | anthropic/claude-sonnet-4.6 and anthropic/claude-opus-4.8 |
| Codex | tokenrouter | /v1/responses | openai/gpt-5.3-codex and openai/gpt-5.5 |
| Hermes | openai-api | /v1/chat/completions | anthropic/claude-haiku-4.5 |
The intersection: connecting adds no name to a picker
The router's LLMTR table is not everything the endpoint publishes. It is the intersection with the router's own model catalogue. That narrowing is deliberate and reduces to one sentence: connecting the provider adds no name to any picker, it only makes models that are already there reachable through Türkiye. What changes for the user is not the names in the list but the route behind them.
The measurement is on record. On 19 August 2026 the public catalogue, which needs no key to read, was fetched: of 245 published ids, 131 clear the agent-loop bar the router states. The 107 of those served only by LLMTR, including the models hosted in Türkiye, are held back as a separate proposal until the router's capability test can scan this catalogue too.
Four rules did the filtering. They are written down so the next person can re-run the measurement instead of trusting a number.
| Exclusion rule | What it keeps out of the table | Example |
|---|---|---|
| Agent-loop bar | Identifiers that do not emit text or do not declare a tools parameter | Embedding, image, video and speech models |
| Dated snapshot | A second, date-pinned name for a model already in the list | A date-pinned qwen-plus copy while qwen-plus itself is listed |
| Transport match | Identifiers published on an endpoint other than the one the harness actually drives them over | Hermes drives the gpt-5 family over /v1/responses while the id is published for chat only |
| It has to answer | Identifiers that did not respond when called twice on the measurement day | A catalogue entry is an advertisement; the answer is the product, and the reading is that day's |
Mapping short names to canonical identifiers
Harnesses keep model names in short form while LLMTR expects a provider-qualified canonical identifier, so the router carries a mapping between the two. The rule is simple: the canonical identifier is the short name with the provider prefix in front of it.
Two rows sit outside that rule and the file states why. When the router already defines a canonical identifier for a model, that identifier is kept instead of minting a new name. One model appearing twice in the picker under two names was judged worse than a name that reads a little oddly.
An excerpt of the mapping between harness-side short names and LLMTR canonical identifiers
claude-sonnet-5 -> anthropic/claude-sonnet-5
claude-opus-4.8 -> anthropic/claude-opus-4.8
claude-haiku-4.5 -> anthropic/claude-haiku-4.5
gpt-5.3-codex -> openai/gpt-5.3-codex
gpt-5.5 -> openai/gpt-5.5
gemini-3.6-flash -> google/gemini-3.6-flash
deepseek-v4-pro -> deepseek/deepseek-v4-pro
kimi-k3 -> moonshot/kimi-k3
qwen3.8-max -> qwen/qwen3.8-max
minimax-m3 -> minimax/minimax-m3
# the two rows that sit outside the rule
hunyuan-3 -> tencent/hy3
nemotron-3-ultra -> nvidia/nemotron-3-ultra-550b-a55b
Deriving the intersection yourself
The list in the table is a photograph of the measurement day. Rather than waiting for it to be updated when the catalogue changes, derive the intersection yourself: the public catalogue reads without a key, so the check costs you no credentials.
The short listing below pulls the identifiers from the endpoint and writes them out for comparison with your harness-side list. Presence in the list is not sufficient on its own; check the transport match as well, because calling an identifier on the wrong endpoint produces a picker row that fails on send.
Read the public catalogue without a key and diff it against your harness list
# The public catalogue needs no key
curl -s https://llmtr.com/v1/models \
| jq -r '.data[].id' | sort > llmtr-ids.txt
# Canonicalise your harness-side short names, then compare
sort harness-canonical-ids.txt > harness-ids.txt
comm -12 llmtr-ids.txt harness-ids.txt # intersection: drivable today
comm -13 llmtr-ids.txt harness-ids.txt # in the harness, absent at the endpoint
Codex 0.148.0 and the gpt-5.6 family: a limit that is not the provider's
The live turns surfaced one more thing, recorded separately so the next person does not have to rediscover it. Driven by Codex, gpt-5.6-sol, gpt-5.6-terra and gpt-5.6-luna finish a turn but are never offered tools; the model answers that it has no shell and stops.
The cause is the client, not the provider. Pointed at a local endpoint that records what it is sent, codex 0.148.0 emits the request for these three identifiers with no tools and no instructions field at all, while the same binary against the same endpoint sends eleven tools and a long instructions block for gpt-5.5. Nothing ever reaches the provider for it to drop. The family first attempts a WebSocket session and carries a window id header, so it appears to expect tools and instructions to be established on the connection; falling back to plain HTTPS it sends neither.
Driven by Hermes over the same endpoint and the same base URL, those three identifiers keep their tools. So the limit is not specific to one provider: it applies to every non-OpenAI provider. No per-vendor workaround was written into the router; the situation was recorded as it is.
- For a tool-using turn under Codex, gpt-5.3-codex and gpt-5.5 completed real turns on the measurement day
- To drive the gpt-5.6 family with tools, use the Hermes side instead
- To confirm the client is at fault, point it at a local endpoint that logs requests and check whether a tools field is present
- This observation belongs to codex 0.148.0; it was not verified on other versions
Frequently asked questions
Does enabling the LLMTR connection add new names to my harness model picker?
No. The router's LLMTR table is deliberately the intersection with its own model catalogue. The connection makes models already present in the picker reachable through Türkiye; it opens no new rows. Identifiers served only by LLMTR are held back as a separate proposal.
Do I need a separate key for Claude Code, Codex and Hermes?
No. All three harnesses use the same connection record with the same base URL and the same key. The only difference is the endpoint the request reaches: Claude Code uses the messages endpoint, Codex uses responses and Hermes uses chat completions.
Why does hunyuan-3 map to tencent/hy3 in the table?
Because the router already defined a canonical identifier for that model. Prefixing the short name would have minted a second name and shown one model as two picker rows. A single name that reads oddly causes fewer problems than a duplicate entry.
How current is the model list in that table?
It rests on the 19 August 2026 measurement and is a photograph of that day. The intersection shifts as the catalogue changes. To see the current state, read the public catalogue without a key and diff it against your own harness list; the listing in this guide gives the commands.