Commit Graph
117 Commits
Author SHA1 Message Date
yetone 73b650b281 routing: a request a group's rule sent to a member that can't take it fails over to the members the rules after it that match too name, in their order, before the group's others (#112) — two rules for images, the first's model out of quota, now go on to the second's vision model rather than the group's text-only first; and while the first's rests, the second's goes first. The trace names who comes next, and says a turn stays with whoever took it, not the model the rule named 2026-09-27 20:28:38 +08:00
yetone 90fd780eef grok: served straight through the Responses API the grok CLI talks to, not the CLI run behind the gateway — each request goes to cli-chat-proxy.grok.com with the CLI's sign-in and version, as the CLI's own do: the conversation as it is, the caller's tools, images and all, relayed to a Responses client and translated for the rest. The models are the backend's own list, with each one's context window and efforts, and Grok searches the web by itself. A further account signs with its own home. Tested live from a sandbox: Claude Code and OpenCode on grok-4.7 list, read and write files through several tool rounds; an image and a web search answered 2026-09-27 19:41:12 +08:00
yetone 4e83dc6be8 web: magpie web knows its version and updates itself as the app does — the badge's restart puts the new version in and runs it in the old one's place with the same arguments and key, so the open page reloads onto it (#111); routing: a gateway served by magpie serve, which has no window, is told apart from another magpie's window, and the routing view says to stop it so this magpie serves the gateway and shows routing live (#110) 2026-09-27 19:39:45 +08:00
Zhizhen HeandClaude Code 41de0d87eb gateway: count_tokens counts the masked prompt, and a count that cannot be had is told apart — the count Claude Code asks for before every turn went to the vendor as the conversation was written, so Mask secrets was defeated on every one; it now goes masked, the same prompt the turn it counts sends. An endpoint that doesn't implement counting (404/405/501, or a 400 that says so, in English or Chinese) falls back to the local estimate as before; a limited key or a broken connection yields to the next checked key and rests the one that failed, as a generation would, while a client's own cancellation rests nothing; and a real error — a wrong key, a prompt past the model's context, an image it won't take — reaches the agent. The gateway tests are isolated from the machine now (a temp HOME, inert stubs for the sign-in CLIs and the keychain) so a cache that outlives a test can't reach a real login, and the redaction tests' cold starts run in parallel. (#103)
Co-authored-by: Claude Code <noreply@anthropic.com>
2026-09-27 18:58:23 +08:00
yetone ab7dd01506 codex: /fast works on a routing group too — a group with a ChatGPT account's GPT model in it is listed to Codex with the Fast tier, and the service_tier Codex then sends is kept through the group's routing and passed on to OpenAI's own backends (a ChatGPT account, api.openai.com) only; before, the request was rebuilt without it and Fast did nothing 2026-09-27 17:51:38 +08:00
yetone b5340c7033 grok, cursor: a run ends with its caller — one whose client left mid-reply (Esc, /clear, Claude Code quit) is stopped at once instead of answering no one, one waiting on tool results that never come is let go after 5 minutes, not 30, and a stop kills what the CLI started too (its own session on Mac and Linux, a job on Windows). A tool that runs longer still gets its answer, from a run told the whole conversation. Runs are stopped with the gateway 2026-09-27 17:42:01 +08:00
yetone c96cfa0ba0 codex: warm-up — a ChatGPT account's next window can be started as soon as the last one resets, by a one-word request to the mini model at low effort, so the 5-hour (or weekly) clock is already running when work begins; Settings → Codex 预热: off, weekly, or weekly and 5-hour. Checked every 5 minutes, only a window seen idle (nothing used, its reset a full span away) is warmed, three tries per reset 2026-09-27 17:36:20 +08:00
yetone 154d071699 routing: a group's effort can be picked per turn by any classifier, not only TypeSafe's Jev — a model that isn't a decision API (Jev Router on OpenRouter, Jev on OpenCode Zen, or any small fast model) is asked in words which of low, medium, high or xhigh the turn needs, in a call of its own beside the intent's, and told what the turn before needed; the routing editor offers every model for it 2026-09-27 16:11:23 +08:00
yetone ce8d0e065c devin: served straight through Devin's API, not the CLI over ACP — each request goes to the GetChatMessage RPC the devin CLI itself uses, with its sign-in: the conversation as it is, the caller's tools, images and all, and the reply streamed back text, thinking and tool calls as they come. A family's name is sent as its variant at the effort asked for; Adaptive and Fusion, which the API doesn't serve, are no longer listed. The instructions go at the head of the first message, as Kiro's do, since Devin turns away Claude Code's own in its system field. Tested live from a sandbox: Claude Code and OpenCode on Opus 5.5, SWE-2 and GPT-6 Sol list, read and write files through several tool rounds; images and a tool round trip answered on Claude, SWE, GPT and Gemini models 2026-09-27 15:30:24 +08:00
yetone 1d4930dd8e agents: Alma is known as Alma, not ai-sdk — its requests go out with the AI SDK's User-Agent and nothing of Alma's, so magpie's provider in Alma now carries the key magpie-alma and the gateway knows an agent by a magpie-<agent> key before its User-Agent (usage, the routing view, the models it is shown). An Alma wired before gets the key on the next sync, the key alone sent, as a baseURL would have Alma drop its models 2026-09-27 15:28:33 +08:00
herrkaefer c46840a0b0 gateway: validate requests before routing Codex models (#99) 2026-09-27 14:46:22 +08:00
yetone 6cd76567aa devin: a turn Devin ends because one of its own tools was refused goes on — it sometimes lists the magpie tools, finds the list long and reaches for its own grep or read to look through it, which are off, and Devin then ends the turn (agent_stopped, tool_rejected) as if it were done, so Claude Code showed half an answer and no error. magpie now tells it to carry on with the magpie tools, up to three times, and the prompt says they are already there to call. Reproduced in interactive Claude Code on devin/swe-2 and checked there: the refused read is followed by the rest of the task, files written and tests run 2026-09-27 14:42:54 +08:00
yetone 17f289dd4a gateway: shared on the local network from Settings — Share on local network puts the gateway on every interface at its port, at once and without a restart, so agents on other computers use magpie's models; a request from another machine must carry the API key magpie makes for it (shown with the addresses, copied, renewed in place), and goes on as this computer's own, which need none as before. Off puts it back on loopback. Tested live from a Windows machine on the LAN: no key or a wrong one is turned away (401), the key lists the models and a chat completion goes through to the vendor with the provider's own key. Saving the Settings page no longer drops what other pages keep in settings.json (models' names and levels, who sees which, the network sharing) 2026-09-27 13:48:57 +08:00
yetone 7c4a5d114c providers: an OpenCode model is sent on the API models.dev says it's served on — Grok and GPT on /responses, Claude and MiniMax on /messages — so Claude Code asking OpenCode Go for Grok goes straight to /responses; OpenCode's "Model does not support this protocol" is also taken as the model being served elsewhere 2026-09-27 12:51:43 +08:00
yetone 6cd4344a58 gateway: OpenCode's "Model … is not supported for format anthropic" (and oa-compat) is taken as the model being served on another endpoint, so Claude Code asking OpenCode Go for a Grok model — served on /responses only — goes on to it rather than stopping at the first refusal, and later requests go straight there 2026-09-27 12:34:36 +08:00
yetone cae88f031c agents: Qoder — magpie's models in Qoder's CLI (1.1) as a "magpie" custom provider in ~/.qoder/settings.json ($QODER_CONFIG_DIR): picking magpie/<model> or a routing group writes the provider, with every magpie model, its context, output, images and the efforts it takes, and makes it model.name; the effort is model.reasoningEffort. The comments and entries of the user's settings stay, and stepping out of magpie takes its provider away and puts back the model the user had. Qoder offers custom providers only to a signed-in account whose plan has BYOK. It asks every model for enable_thinking, which OpenAI's API refuses, so that is dropped for openai.com. Tested live: magpie → settings.json → qodercli -p → magpie's gateway → upstream, the reply streamed back 2026-09-27 06:28:14 +08:00
e136cf9a27 gateway: Gemini PDF and audio files kept on text-only routes (#95)
* gateway: keep Gemini document and audio file data on text-only routes

* fix(gateway): avoid translating Gemini files as images

* fix(gateway): preserve Gemini files for Code Assist while translating safely

* fix(gateway): retain Gemini file placeholder for Claude subscriptions

---------

Co-authored-by: Instinct Agent <agent@instinct.com>
Co-authored-by: Charan Rathore <charan-rathore@users.noreply.github.com>
2026-09-27 04:24:03 +08:00
yetone cf60231465 gateway: a web search magpie runs for Claude Code is told as Anthropic's own — a server_tool_use block with its query and a web_search_tool_result block with the pages found — so Claude Code's WebSearch counts it and lists its links, where it said "Did 0 searches" with the answer alone. This holds for magpie's search tool on any provider, the Claude subscription's WebSearch and magpie's search tool run inside it (no longer passed to the client as a call), and a Responses provider's own web_search_call, whose sources are asked for. Tested live with Claude Code against a Claude subscription, Grok and Codex: each search comes back with its links 2026-09-27 03:41:53 +08:00
5bad1d117f gateway: count usage from multiline and final SSE events (#94)
* gateway: count usage from multiline and final SSE events

* fix(gateway): count single-newline SSE events alongside multiline data

---------

Co-authored-by: Instinct Agent <agent@instinct.com>
Co-authored-by: Charan Rathore <charan-rathore@users.noreply.github.com>
2026-09-27 03:29:23 +08:00
yetone b3f613acb4 gateway: Claude Code with ENABLE_TOOL_SEARCH can search the web — it defers WebSearch and loads it with ToolSearch, whose result is a tool_reference magpie read as nothing, and a Claude subscription's run, told its tools as it began, went on without the tool the client had just loaded, so the model said WebSearch would not load. A tool_reference is now told as the tool being loaded, DeferredToolPlaceholder is left out, and a subscription run whose client now offers a tool it lacks is replaced by one told the conversation with it. Tested with Claude Code on Opus (Claude subscription), Grok and Codex models 2026-09-27 02:53:39 +08:00
yetone 0f59ca2fc0 routing: a turn a rule sent to one of a group's models stays on it for its tool rounds when another member is on the same account — the conversation was kept to the account that answered, so its next request went to that account's first member: Claude Code's turn Jev sent to codex/gpt-5.5 had its tool results answered by claude/claude-sonnet-5, both served by one provider (as Opus and Sonnet on one Claude sign-in are). And a group whose intents are levels has every turn at one of them however torn Jev is: 继续,再加一个基准测试 after a 复杂任务 came out 0.54 to 0.46, below the confidence counted, and went to the group's first member; a torn turn now stays at the level of the one before, or takes the likelier. Tested with Claude Code through Jev and the real Claude and ChatGPT subscriptions 2026-09-27 02:25:24 +08:00
yetone 68408d2d00 gateway: a web search a client offers — Claude Code's WebSearch, Codex's web_search, a Chat client's web_search_options, Gemini's googleSearch — is done for every provider, not only the Claude subscription. A provider that searches by itself is asked to (a Codex account and OpenAI/xAI with Responses' web_search, Anthropic's API with web_search_20250305, OpenRouter with its web plugin); any other model — DeepSeek, Kiro, Grok, Cursor, Devin, a relay — is given a web_search tool of magpie's, which the first provider set up that can search answers with a small model of its (the Claude subscription's Haiku, then Codex, Anthropic, OpenAI, OpenRouter), and the model is asked again with what was found: the client sees one reply and no tool call. A request offering search is no longer relayed as is to a provider that would ignore the tool. Tested live: Claude Code → DeepSeek with WebSearch (2 turns), Responses and Chat clients → DeepSeek, Kiro and Grok, with the Claude subscription, Codex and OpenRouter each doing the search 2026-09-27 02:21:49 +08:00
yetone f2b88ad5b4 jev: a group whose intents are levels has every message in one of them — Jev is asked once per set whether the kinds are levels (a noul, kept), and for such a set each turn is read from a choice among the kinds alone, as with a "none of these" option it put 你好 in none; Jev is also told the kind and the reasoning of the turn before, so 继续 after a 复杂任务 stays complex at its effort. Tested live against System One and OpenRouter: 你好 → nano/low, a deadlock analysis → mini/high (xhigh capped), 继续 → mini, 你是谁 after it → nano/low, Claude Code and Responses requests → the effort picked is what is sent 2026-09-27 01:53:04 +08:00
yetone 6fd85894ea routing: Jev (TypeSafe's System One) as a group's classifier — asked once as a turn begins which of the rules' intents the message is, with how sure it is, and, for a group with effort auto, how hard the turn is to think about; the turn's requests then ask their model for that reasoning, where the agent asked for any. TypeSafe is a decision provider: it serves no conversation and is no group's member 2026-09-27 01:53:04 +08:00
yetone c576153dd7 gateway: a web search asked of a Claude subscription is done — Claude Code's own WebSearch tool asks for Anthropic's web_search, which magpie dropped as a server tool while running claude -p with no tools of its own, so the model answered unsearched. The run now has Claude Code's WebSearch when the client offers web_search; its call and result stay inside the turn, the client hearing one reply whose usage counts both requests 2026-09-27 01:39:22 +08:00
yetone a5a8d29ba4 providers: a model list that says which endpoints each model is served on (supported_endpoints, as Command Code's does) is heeded for every provider, not Copilot's alone — a live-fetched list left it out, so Claude Code's request for Command Code's deepseek went to its /messages, which serves only its Claude models, and was turned away; it now goes to /chat/completions, translated, and a Claude model asked for over Chat goes to /messages. /messages and /v1/messages both name Anthropic's. Issue #93 2026-09-27 01:29:33 +08:00
yetone 4f22d103d3 providers: Kiro is served through Kiro's own API, as pi-provider-kiro does, not kiro-cli over ACP — magpie reads the sign-in kiro-cli or the Kiro IDE keeps (Google, GitHub, AWS Builder ID / IAM Identity Center, or an external IdP), or takes a Kiro API key saved on the provider, and calls generateAssistantResponse itself: the caller's system prompt, whole history, tools, tool calls and results and images go to the model as they are, with no agent's own prompt and tools between, and its event stream comes back as text, thinking (asked for with Kiro's thinking mode), tool calls streamed as they are written, and usage. kiro-cli no longer has to be installed. A token near its end is refreshed — by kiro-cli when it is its sign-in, else directly, the new one written back where it came from — and one Kiro turns down is refreshed once and the call tried again. History is made the shape Kiro takes: it opens and ends with the user, every call is answered in the next message (an interrupted one said to be so), orphan results go, tools the conversation used but no longer offers are described, ids Kiro rejects are hashed, only the latest images are sent again. Kiro's usage limit is a 429, a prompt too long the error the rest of magpie takes for it. The models are Kiro's list with their context and image support; the account is its email and plan; the Usage page shows its credits and free trial. Issue #90 2026-09-27 01:25:37 +08:00
yetone 050c33b07d providers: Kiro — a Kiro subscription served through the genuine kiro-cli over ACP, as Devin's is: magpie runs kiro-cli-chat acp as an agent of its own written into an empty workspace, whose only tools are the caller's (the magpie MCP helper, trusted; includeMcpJson off), picks the model with session/set_model, streams text and thinking back, and refuses permission for anything but the caller's tools. The account is whoever kiro-cli whoami says is signed in; a Kiro API key saved on the provider goes to the CLI as KIRO_API_KEY, the user's own environment's never. Missing CLI, signed out, MCP turned off by Kiro and ACP errors' data are each said plainly 2026-09-27 00:39:12 +08:00
yetone 47e12df418 models: a provider's model can go by a name of the user's, and offer only some of its reasoning levels — Claude Opus 5.5 · Claude Code can be Opus 5.5, and a model with six levels can offer just low, medium and high. Both are kept in settings.json by provider/model (modelNames, modelEfforts), apart from the vendor's list, so a Refresh, a restart or an upgrade leaves them, and the same model from another provider keeps its own; a provider renamed takes them along. They are applied where the catalog is made, so the gateway's /v1/models, the lists magpie writes into the agents (Codex's model catalog, ZCode, Pi, OpenCode, OMP …) and magpie's own pages all show them, and a change rewrites the agents' lists as any other change of the catalog does. Only what is shown changes: the id, where requests go, and the levels the vendor is asked at stay as they were; a found routing group keeps the model's own name. The provider's editor has Names & levels under its models — a name typed in place, a tick per level, Restore default — and the terminal has magpie model name <provider/model> <name>|--reset and magpie model efforts <provider/model> low,medium,high|--reset. Issue #92 2026-09-26 23:23:45 +08:00
yetone 97d4b3154e gateway: a model's id without a provider in it that names a routing group is served by the group — gpt-6-sol goes where group/gpt-6-sol goes, its cheapest member first and the next when one fails, where it went to the first provider that serves it with no failover; and a found group is named however a vendor spells its model, so grok-4-7 (the group of grok-4.7) is served rather than unknown. With no group it is one provider's as before, a provider/model id is always that provider's, and the Routing view shows the group it went to. Asked on Discord 2026-09-26 23:01:53 +08:00
yetone cd3222d213 intent: a turn that only carries on from the one before — 继续, 好的就这么做, fix that — is of the kind that one was; the classifier is told what it said of the conversation's turn before, as it only saw the new message and put 继续 after a 复杂任务 among the simple ones. A message that asks for something of its own is still classed by itself (你是谁 after a 复杂任务 is 简单任务), and the Routing view says what it was told 2026-09-26 22:51:17 +08:00
yetone 13aa2049a4 intent: a group whose intents are levels — 简单任务 and 复杂任务, how hard a request is — has every message in one of them; the classifier was asked which kind of request a message is, with 0 for none, and put 你是谁 and 你好 in neither, so those turns matched no rule and went to the group's usual route (Claude Opus) rather than the simple one. It now picks the kind that fits best, answering 0 only when a message is plainly none of them: groups by topic still get 0 for a greeting or a poem 2026-09-26 22:39:30 +08:00
yetone 2eb5015df3 accounts: Codex is signed in to its next ChatGPT account when the one it is on has used its allowance up — the Codex app, knowing its account is out, may not send at all, so magpie's failover never saw the turn: now the magpie serving the gateway looks every five minutes and, with another of Codex's accounts on in magpie that has room, switches Codex to the first of those as the Accounts page would (the one out stays on, next in line); a Codex started after that is on it. An account rests by its user, so the one Codex is switched to doesn't take over the rest of the one it left. Asked on Discord 2026-09-26 21:32:42 +08:00
yetone 0f0b8fc87e gateway: Codex on one of its own models moves on to the next ChatGPT account when the one it is signed in to has used its allowance up — with more of Codex's accounts on in magpie, Codex's requests go through magpie even for its own models (openai_base_url kept, and put back or taken out as accounts are switched on or off) and are served as the codex subscription's, which fails over as its other models do; the account out of allowance rests until ChatGPT says it resets (resets_at), up to an hour at a time. Asked on Discord 2026-09-26 21:11:42 +08:00
yetone 4790c06b02 usage: each call keeps where it went — the provider's host, or the account it was — so a provider id later pointed somewhere else no longer puts the earlier place's tokens and cost under the new one: a model's usage is told apart by host (provider/model @ host) when its provider went to more than one place in the period, or to somewhere other than where it goes now. Asked on Discord 2026-09-26 19:03:14 +08:00
Jeremy Levartovsky 48071e9ab9 gateway: a Claude reply cut off by the context window says it was cut off (#88)
Claude 4.5 and later stop with model_context_window_exceeded when the
reply runs into the context window before max_tokens. It was read as a
normal end, so a Chat Completions client got finish_reason "stop" and a
Responses client status "completed" for a truncated reply; it now
reads as length, as max_tokens does.
2026-09-26 18:59:26 +08:00
yetone d05763a5dc opencode: each model of magpie's carries its window (limit.context), a group's context= with it, so OpenCode compacts when the model needs it and not at its own guess; and magpie sync puts magpie's provider back into opencode.json when a model of magpie's is chosen there but the provider is gone. /v1/models says each model's window too (context_window, context_length, max_input_tokens, max_output_tokens). Asked on Discord 2026-09-26 18:50:04 +08:00
yetone 5dbd28c39e zcode: what you choose in ZCode for magpie stays — the provider turned off stays off, its rule stays where you moved it, and a rule you removed isn't put back on the next sync. gateway: a model that always thinks (GLM-5.3 answers 1210 to thinking turned off) is asked again with thinking left to it, rather than failing. Asked on Discord 2026-09-26 18:45:49 +08:00
yetone a1dd2b1222 routing: a turn on one of Codex's own models (gpt-5.5 and the rest, which go to ChatGPT with Codex's own sign-in) shows in the Routing view's live trace as those on magpie's models do — before, only magpie's were traced, so Codex on its own models showed nothing going on. Asked on Discord 2026-09-26 18:31:46 +08:00
yetone a77f7c1fcc gateway: a test that when a rule's vision model fails, the text-only member that answers is sent the conversation without its older images (#87) 2026-09-26 18:25:16 +08:00
Charan RathoreandCharan Rathore d583acaf46 fix: avoid sending group images to text-only fallbacks (#87)
Co-authored-by: Charan Rathore <charan-rathore@users.noreply.github.com>
2026-09-26 18:25:34 +08:00
yetone d2da93ef11 group: an unlisted text-only model still keeps images out of its group — whether a group takes images was asked of the listed models only, so an image went to a member kept out of the pickers that can't see it; now every member counts, as for the context since #86 2026-09-26 17:52:54 +08:00
Charan RathoreandCharan Rathore 70a37c63f1 fix: count unlisted group members when routing long turns (#86)
Co-authored-by: Charan Rathore <charan-rathore@users.noreply.github.com>
2026-09-26 17:52:49 +08:00
yetone 7b69b22a76 quota: what is left of every subscription, plan and key balance, for scripts and agents — magpie quota [<provider>] [--json] prints each window's use and remaining percent and when it starts again, and each key's balance; the gateway answers the same at GET /v1/magpie/quotas (only to this machine, as it names the accounts), so an agent can pick where to send its work (#85) 2026-09-26 17:22:00 +08:00
yetone 73be42eca8 models: each agent can be shown only some of them — a provider or group gets a family (magpie provider set relay-x family=relay), and magpie visible zcode relay shows zcode only that family's models (or named providers and groups): in the files magpie writes for it and in what /v1/models answers it, told apart by its User-Agent; magpie models zcode lists what it is shown and what is kept from it, and magpie visible zcode all shows it everything again. A model kept from an agent is still served when asked for by name. Asked for on Discord 2026-09-26 16:02:29 +08:00
yetone 1c960ac62e claude: a turn in which Claude answered with two tool calls no longer gets stuck — Claude Code makes its MCP calls one after another, so the second was made only once the first had its result, while the agent ran both and sent both results back at once; the second result, for a call not made yet, was dropped, and Claude waited for it while the agent waited for Claude. A result sent before its call is now kept and handed over as the call is made, and only the results sent since the last answer are looked at (#84) 2026-09-26 15:51:13 +08:00
yetone 189c8949ed codex: one of Codex's own models picked while Codex is signed in with an API key OpenAI refuses (one a relay issued, left in ~/.codex/auth.json) failed with OpenAI's bare 401 "Incorrect API key provided" on every turn; the refusal now says it is Codex's own sign-in that was refused and to pick one of magpie's models in Codex or sign Codex in again 2026-09-26 15:14:33 +08:00
yetone 67c539d5da gateway: secrets can stay on the machine — with Settings › Privacy › Mask secrets on, API keys, private keys, tokens, JWTs and the passwords of connection strings and assignments in what an agent sends go to the vendor as placeholders like {{API_KEY_k3v9x2mq}}, and what the vendor answers has them back, streams included (a placeholder split between two events is put back whole, a tool call's arguments get the value escaped); personal data (emails, phone numbers, ID and bank card numbers, checksum-checked) and the user's own words can be masked too. A placeholder is the same for the same value every time, so a conversation's history and what the vendor cached of it stay the same turn after turn, and the request log shows what the vendor saw. Suggested on X as maskit's idea; written from scratch 2026-09-26 14:21:14 +08:00
yetone 67056512e0 codex: back on one of Codex's own models after another vendor's, the conversation goes on — another vendor's reasoning (an rs_… id with nothing sealed in it, as Volcengine writes it) went on to OpenAI, which keeps nothing (store is false), looked the id up and answered 404 "Item with id 'rs_…' not found" on every turn and compaction; it now stays out, and an item OpenAI still refuses (sealed content it can't verify, an id it doesn't have) is taken out and the rest asked again instead of the session stuck on it 2026-09-26 13:59:01 +08:00
yetone d4135b55e1 group: a routing group's models can be another group — group/<id> as a member, routed in its place by its own routing and rules, so Coder can be deepseek-v4-pro then Fast, a Coder rule can send a turn to group/fast and Fast's own rules pick within it; a group can't be in itself directly or through another (refused on save, left out of the picker), groups go at most 8 deep, and one can't be removed while another has it. The Routing tab shows a group in a group as a heading with its models under it, the packet going through it, and the log says what each group's rules and classifier decided 2026-09-26 13:41:17 +08:00