captchakraken 2.8.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/AGENTS.md +112 -0
  2. package/NOTICE +36 -0
  3. package/dist/cli-invocation.d.ts +9 -40
  4. package/dist/cli-invocation.js +16 -49
  5. package/dist/errors.d.ts +7 -27
  6. package/dist/errors.js +6 -25
  7. package/dist/humanize.d.ts +56 -240
  8. package/dist/humanize.js +159 -415
  9. package/dist/index.d.ts +5 -129
  10. package/dist/index.js +4 -98
  11. package/dist/kinds.d.ts +135 -0
  12. package/dist/kinds.js +70 -0
  13. package/dist/limits.d.ts +0 -48
  14. package/dist/limits.js +2 -48
  15. package/dist/model-name.d.ts +4 -19
  16. package/dist/model-name.js +32 -40
  17. package/dist/playwright-types.d.ts +25 -95
  18. package/dist/playwright-types.js +0 -23
  19. package/dist/puppeteer-adapter.d.ts +4 -43
  20. package/dist/puppeteer-adapter.js +22 -58
  21. package/dist/python-command.d.ts +0 -41
  22. package/dist/python-command.js +0 -37
  23. package/dist/selectors.d.ts +54 -0
  24. package/dist/selectors.js +117 -0
  25. package/dist/slide-geometry.d.ts +2 -29
  26. package/dist/slide-geometry.js +18 -44
  27. package/dist/solver.d.ts +112 -539
  28. package/dist/solver.js +1088 -2950
  29. package/dist/timing.d.ts +7 -40
  30. package/dist/timing.js +4 -36
  31. package/dist/token-usage.d.ts +1 -7
  32. package/dist/token-usage.js +10 -95
  33. package/dist/types.d.ts +77 -483
  34. package/dist/watcher.d.ts +5 -78
  35. package/dist/watcher.js +0 -56
  36. package/package.json +12 -4
  37. package/python/AGENTS.md +122 -0
  38. package/python/NOTICE +36 -0
  39. package/python/README.md +1 -2
  40. package/python/examples/README.md +10 -0
  41. package/python/examples/_harness.py +4 -73
  42. package/python/examples/demoHcaptcha.py +0 -13
  43. package/python/examples/demoRecaptcha.py +0 -10
  44. package/python/examples/watch_playwright.py +1 -26
  45. package/python/examples/with_local_gguf.py +28 -0
  46. package/python/examples/with_playwright.py +0 -19
  47. package/python/pyproject.toml +68 -19
  48. package/python/src/captchakraken/__init__.py +37 -45
  49. package/python/src/captchakraken/action_types.py +14 -36
  50. package/python/src/captchakraken/cli.py +262 -879
  51. package/python/src/captchakraken/config.py +60 -146
  52. package/python/src/captchakraken/errors.py +17 -95
  53. package/python/src/captchakraken/humanize.py +174 -597
  54. package/python/src/captchakraken/image_processor.py +16 -235
  55. package/python/src/captchakraken/keyframes.py +69 -434
  56. package/python/src/captchakraken/kinds.py +152 -0
  57. package/python/src/captchakraken/models.json +79 -21
  58. package/python/src/captchakraken/overlay.py +65 -498
  59. package/python/src/captchakraken/page_solver.py +1123 -2564
  60. package/python/src/captchakraken/planner.py +70 -428
  61. package/python/src/captchakraken/prompts.py +45 -261
  62. package/python/src/captchakraken/selectors.py +184 -0
  63. package/python/src/captchakraken/server_manager.py +7 -58
  64. package/python/src/captchakraken/solver.py +128 -522
  65. package/python/src/captchakraken/timing.py +20 -74
  66. package/python/src/captchakraken/tool_calls/board_painted.py +47 -0
  67. package/python/src/captchakraken/tool_calls/find_checkbox.py +20 -30
  68. package/python/src/captchakraken/tool_calls/find_grid.py +186 -1339
  69. package/python/src/captchakraken/tool_calls/track_piece.py +81 -57
  70. package/python/src/captchakraken/updater.py +7 -76
  71. package/python/src/captchakraken/watcher.py +12 -101
  72. package/scripts/copy-python.mjs +10 -10
  73. package/scripts/setup-python.js +1 -21
  74. package/dist/trajectory.d.ts +0 -58
  75. package/dist/trajectory.js +0 -266
  76. package/python/src/captchakraken/tool_calls/move_indicator.py +0 -431
  77. package/python/src/captchakraken/trajectory.py +0 -284
@@ -0,0 +1,152 @@
1
+ """Every closed set of names the two ports agree on. Mirrored in js/src/kinds.ts; keep both in the same order."""
2
+ from enum import StrEnum
3
+
4
+
5
+ class Vendor(StrEnum):
6
+ """`puzzle_source` everywhere. UNKNOWN must stay permissive: GeeTest and Prosopo report it, and so does the offline grader."""
7
+
8
+ HCAPTCHA = "hcaptcha"
9
+ RECAPTCHA = "recaptcha"
10
+ TURNSTILE = "turnstile"
11
+ GEETEST = "geetest"
12
+ TENCENT = "tencent"
13
+ YIDUN = "yidun"
14
+ YANDEX = "yandex"
15
+ LEMIN = "lemin"
16
+ PROSOPO = "prosopo"
17
+ MTCAPTCHA = "mtcaptcha"
18
+ BOTDETECT = "botdetect"
19
+ UNKNOWN = "unknown"
20
+
21
+
22
+ class ActionKind(StrEnum):
23
+ CLICK = "click"
24
+ DRAG = "drag"
25
+ TYPE = "type"
26
+ WAIT = "wait"
27
+ DONE = "done"
28
+
29
+
30
+ class PixelAnswerKind(StrEnum):
31
+ """What the pixel parser reads off the model's text before it becomes an action."""
32
+
33
+ CLICK = "click"
34
+ DRAG = "drag"
35
+ SLIDE = "slide"
36
+ TYPE = "type"
37
+
38
+
39
+ class RetryMode(StrEnum):
40
+ MISSED_TILES = "missed-tiles"
41
+
42
+
43
+ class SettleVerdict(StrEnum):
44
+ SETTLED = "settled"
45
+ ANIMATED = "animated"
46
+ TIMEOUT = "timeout"
47
+
48
+
49
+ class PaintVerdict(StrEnum):
50
+ PAINTED = "painted"
51
+ BLANK = "blank"
52
+ UNKNOWN = "unknown"
53
+
54
+
55
+ class KeyframeMode(StrEnum):
56
+ """EVEN means the slicer could not prove recurrence, not that the board is still."""
57
+
58
+ STATIC = "static"
59
+ CYCLE = "cycle"
60
+ EVEN = "even"
61
+
62
+
63
+ class RecaptchaBanner(StrEnum):
64
+ SELECT_MORE = "select-more"
65
+ DYNAMIC_MORE = "dynamic-more"
66
+ REJECTED = "rejected"
67
+
68
+
69
+ class PromptFamily(StrEnum):
70
+ """One expert per family; `pixel` is spelled to match ckgate's prompt markers."""
71
+
72
+ PIXEL = "pixel"
73
+ GRID = "grid"
74
+ VIDEO = "video"
75
+ TEXT = "text"
76
+
77
+
78
+ class Availability(StrEnum):
79
+ """PRIVATE is not a softer LICENSED: refusing a private model early would stop an authorised token from fetching weights."""
80
+
81
+ PUBLIC = "public"
82
+ PRIVATE = "private"
83
+ LICENSED = "licensed"
84
+
85
+
86
+ class HumanizationMode(StrEnum):
87
+ MOUSE = "mouse"
88
+ MOBILE = "mobile"
89
+ NONE = "none"
90
+
91
+
92
+ class PauseKind(StrEnum):
93
+ TAP = "tap"
94
+ BETWEEN = "between"
95
+ GRAB = "grab"
96
+ DROP = "drop"
97
+ PROBE = "probe"
98
+ SETTLE = "settle"
99
+ KEY = "key"
100
+
101
+
102
+ class Outcome(StrEnum):
103
+ SOLVED = "solved"
104
+ FAILED = "failed"
105
+
106
+
107
+ class Phase(StrEnum):
108
+ """Timing phases. Only INFERENCE and MOUSE are productive; everything else is waiting."""
109
+
110
+ INFERENCE = "inference"
111
+ MOUSE = "mouse"
112
+ SCREENSHOT = "screenshot"
113
+ DETECT = "detect"
114
+ SETTLE = "settle"
115
+ BURST = "burst"
116
+ GRID = "grid"
117
+ GRID_LOAD = "grid-load"
118
+ FADE_WAIT = "fade-wait"
119
+ BOARD_PAINT = "board-paint"
120
+ HCAPTCHA_IMAGES = "hcaptcha-images"
121
+ AWAIT_NEXT_ROUND = "await-next-round"
122
+ AWAIT_VERDICT = "await-verdict"
123
+ POST_SUBMIT_DELAY = "post-submit-delay"
124
+
125
+
126
+ class FrameRole(StrEnum):
127
+ CHECKBOX = "checkbox"
128
+ CHALLENGE = "challenge"
129
+ UNKNOWN = "unknown"
130
+
131
+
132
+ class LabelPosition(StrEnum):
133
+ TOP_LEFT = "top-left"
134
+ CENTER = "center"
135
+ TOP_RIGHT = "top-right"
136
+ BOTTOM_LEFT = "bottom-left"
137
+ BOTTOM_RIGHT = "bottom-right"
138
+
139
+
140
+ class ErrorCode(StrEnum):
141
+ """The hosted API's machine-readable codes. Unknown codes still carry the server's message through; this is not a filter."""
142
+
143
+ MISSING_API_KEY = "missing_api_key"
144
+ INVALID_API_KEY = "invalid_api_key"
145
+ ACCOUNT_SUSPENDED = "account_suspended"
146
+ INSUFFICIENT_CREDITS = "insufficient_credits"
147
+ RATE_LIMITED = "rate_limited"
148
+ SOLVE_ABANDONED = "solve_abandoned"
149
+ UNRECOGNIZED_PROMPT = "unrecognized_prompt"
150
+ INVALID_REQUEST = "invalid_request"
151
+ REQUEST_TOO_LARGE = "request_too_large"
152
+ UPSTREAM_UNAVAILABLE = "upstream_unavailable"
@@ -62,10 +62,10 @@
62
62
  " authorised holder can do: a licensed model has nothing to",
63
63
  " download however good your credentials are. `latest` may",
64
64
  " never be private either — an unpinned self-hoster would get",
65
- " a 401 instead of a model — and check_prompt_parity.py says so.",
65
+ " a 401 instead of a model — and the release parity gate says so.",
66
66
  "",
67
- " \"licensed\" — NOT downloadable by anyone. Reachable only through",
68
- " api.captchakraken.com, or by a holder of a licence we issue.",
67
+ " \"licensed\" — NOT downloadable without a licence we issue. Served to",
68
+ " every account through api.captchakraken.com.",
69
69
  " There is no Hub repo to point CAPTCHA_LORA_ADAPTER at, and",
70
70
  " `captchakraken fetch` refuses rather than 404ing at the Hub.",
71
71
  "",
@@ -73,8 +73,8 @@
73
73
  "place that decides what an unpinned client downloads and what prompts it",
74
74
  "sends. A second list of 'models you may not publish' kept somewhere else",
75
75
  "would be a list that drifts, and the drift is silent in the direction that",
76
- "costs the most. The finetune repo's publisher reads this field and REFUSES",
77
- "to upload a licensed model to the Hub; check_prompt_parity.py refuses to let",
76
+ "costs the most. The publisher reads this field and REFUSES",
77
+ "to upload a licensed model to the Hub; the release parity gate refuses to let",
78
78
  "`latest` be one, because an unpinned self-hoster would resolve to weights",
79
79
  "they cannot obtain.",
80
80
  "",
@@ -102,7 +102,7 @@
102
102
  "",
103
103
  "Each expert name must resolve — through `served_aliases` or as a repo id —",
104
104
  "to a REGISTERED entry declaring the same `prompt_version` and the same",
105
- "`pixel_budget` as the model that routes to it. check_prompt_parity.py",
105
+ "`pixel_budget` as the model that routes to it. The release parity gate",
106
106
  "enforces that. It is what makes aliasing four arms onto one entry safe",
107
107
  "rather than convenient: the four share a base, a schema and a band today",
108
108
  "(measured, not assumed — runs 20260906-0142xx all carry prompt generation",
@@ -121,6 +121,28 @@
121
121
  "routed model in the first place."
122
122
  ],
123
123
  "latest": "CaptchaKraken/CaptchaKraken-Lora-v1.2",
124
+ "_comment_hosted_default": [
125
+ "WHAT THE HOSTED ENDPOINT IS ASKED FOR, which is not what gets DOWNLOADED.",
126
+ "",
127
+ "`latest` is the download default: the repo a self-hoster pulls and serves.",
128
+ "`hosted_default` is the served NAME a client sends when it is talking to",
129
+ "our API. They were one field until Abyss, and one field cannot express",
130
+ "this: Abyss is hosted-only, so naming it as `latest` would point every",
131
+ "self-hoster at weights they are not licensed to download, while leaving it",
132
+ "out would mean the hosted endpoint never serves the better model.",
133
+ "",
134
+ "This is also what makes the rollout version-gated without a version",
135
+ "header. A client that ships this file asks for `abyss`; one that predates",
136
+ "it asks for whatever its own registry says and keeps getting v1.2. Nothing",
137
+ "server-side has to know which is which.",
138
+ "",
139
+ "Only consulted for an endpoint that is OURS \u2014 see config.is_hosted_endpoint.",
140
+ "A self-hosted vLLM, local or remote, resolves from `latest` exactly as",
141
+ "before, because it serves whatever its operator loaded and asking it for",
142
+ "`abyss` would 404."
143
+ ],
144
+ "hosted_default": "CaptchaKraken/Abyss",
145
+
124
146
  "models": {
125
147
  "CaptchaKraken/CaptchaKraken-Lora-v1.2": {
126
148
  "prompt_version": "2",
@@ -132,7 +154,7 @@
132
154
  "min": 518400,
133
155
  "max": 518400
134
156
  },
135
- "note": "PUBLIC on the Hub under Source-Available License v1.1, and the first published generation-2 model. TRAINED with floor 448^2 / ceiling 720^2, but MEASURED best served at a flat 720^2 — floor == ceiling, so every image arrives at exactly one size and the band cannot drift. The band was chosen by sweeping the serving floor over 448/576/640/704/720/736/768/896/1024^2 and scoring each arm: the plateau is 704-736^2, and it falls off above 768^2, where a 400x580 portrait capture runs more than 40% past the ViT's 48x48 position grid. Do not \"just use the ViT's native grid\": 640^2 is where that grid is exactly saturated for a portrait capture, and it scores WORSE than 720^2 — mild position-embedding extrapolation plus real pixels beats staying inside the grid. Was served as `captcha-v12` while absent from this file, which resolved it to generation 1 prompts against generation-2 weights — the failure this file exists to prevent."
157
+ "note": "Generation 2, and the first published one. Trained over a range of input sizes but served at a flat 720^2: floor == ceiling, so every image arrives at exactly one size and the band cannot drift. The band was chosen by sweeping the serving floor and scoring each setting; the plateau is 704-736^2 and it falls off above 768^2, where a portrait capture runs well past the vision tower's position grid. Note that the vision tower's native grid is NOT the best setting — mild position-embedding extrapolation over real pixels beats staying inside the grid."
136
158
  },
137
159
  "CaptchaKraken/Twilight-v1.2-FP8": {
138
160
  "prompt_version": "2",
@@ -142,7 +164,7 @@
142
164
  "min": 518400,
143
165
  "max": 518400
144
166
  },
145
- "note": "CaptchaKraken-Lora-v1.2 merged into Qwen/Qwen3.5-9B at FP8-dynamic. The weights the hosted API answers with. Generation 2, same band as the adapter it is made from: a merge that resolved to a different prompt generation or a different pixel budget than its own LoRA would be the 2026-07-18 regression with extra steps."
167
+ "note": "CaptchaKraken-Lora-v1.2 merged into Qwen/Qwen3.5-9B at FP8-dynamic. Generation 2, same pixel band as the adapter it is made from — a merge that resolved to a different prompt generation or a different band than its own LoRA is exactly the mispairing this file exists to prevent."
146
168
  },
147
169
  "CaptchaKraken/Sunlight-v1.2-AWQ-4bit": {
148
170
  "prompt_version": "2",
@@ -154,13 +176,23 @@
154
176
  },
155
177
  "note": "CaptchaKraken-Lora-v1.2 merged into Qwen/Qwen3.5-9B at AWQ 4-bit. Generation 2. Quantization changes the weights, not the schema they answer in, so the prompt generation and the pixel budget are the adapter's."
156
178
  },
179
+ "CaptchaKraken/CaptchaKraken-v1.2-GGUF": {
180
+ "prompt_version": "2",
181
+ "kind": "merged",
182
+ "lora_name": "CaptchaKraken/CaptchaKraken-v1.2-GGUF",
183
+ "pixel_budget": {
184
+ "min": 518400,
185
+ "max": 518400
186
+ },
187
+ "note": "CaptchaKraken-Lora-v1.2 merged into Qwen/Qwen3.5-9B and converted to GGUF, for llama.cpp and Ollama. ONE entry covers every quantisation in that repo: Q4_K_M, Q8_0 and F16 differ in weight precision, not in the schema they answer or the resolution they read at, so all three share this generation and this band. The vision tower ships beside them as a separate F16 projector and is never quantised — a runtime that loads the weights without it is text-only and cannot see a puzzle at all. Ollama names a model after whatever it was pulled or created as, and that name is what reaches us as `model`; an unrecognised one resolves through CAPTCHA_LORA_ADAPTER instead, which is why the self-hosting guide sets it."
188
+ },
157
189
  "CaptchaKraken/CaptchaKraken_v1.1": {
158
190
  "prompt_version": "1",
159
191
  "kind": "lora",
160
192
  "base_model": "RedHatAI/Qwen3.5-9B-FP8-dynamic",
161
193
  "lora_revision": "main",
162
194
  "lora_name": "captcha",
163
- "note": "Currently served in production. PRIVATE on the Hub, so prompts.json can never be fetched for it — this entry is the only thing that maps it to a prompt generation."
195
+ "note": "Generation 1. Private on the Hub, so prompts.json cannot be fetched for it — this entry is the only thing that maps it to a prompt generation."
164
196
  },
165
197
  "CaptchaKraken/CaptchaKrakenV1_Lora": {
166
198
  "prompt_version": "1",
@@ -168,19 +200,19 @@
168
200
  "base_model": "RedHatAI/Qwen3.5-9B-FP8-dynamic",
169
201
  "lora_revision": "main",
170
202
  "lora_name": "captcha",
171
- "note": "Published before prompts.json existed — no such file in the repo, so it resolves from here."
203
+ "note": "Generation-1 LoRA on Qwen3.5-9B. Superseded by v1.2; kept registered so an existing pin keeps resolving to the prompts it was trained on."
172
204
  },
173
205
  "CaptchaKraken/Sunlight-AWQ-4bit": {
174
206
  "prompt_version": "1",
175
207
  "kind": "merged",
176
208
  "lora_name": "captcha",
177
- "note": "Ships its own prompts.json; this entry keeps it resolvable offline."
209
+ "note": "Generation 1 merged into the base model at AWQ 4-bit."
178
210
  },
179
211
  "CaptchaKraken/Twilight-FP8": {
180
212
  "prompt_version": "1",
181
213
  "kind": "merged",
182
214
  "lora_name": "captcha",
183
- "note": "Ships its own prompts.json; this entry keeps it resolvable offline."
215
+ "note": "Generation 1 merged into the base model at FP8-dynamic."
184
216
  },
185
217
  "CaptchaKraken/Abyss": {
186
218
  "availability": "licensed",
@@ -198,7 +230,7 @@
198
230
  "video": "abyss-video",
199
231
  "text": "abyss-text"
200
232
  },
201
- "note": "IN DEVELOPMENT. Never published, never downloadable — see `availability`. Registered before it exists so the licence gates are armed rather than retrofitted after the first artifact is sitting on a disk: the publisher, the parity gate and `captchakraken fetch` all read this entry, and a rule added the day it is first needed is a rule that was absent on the day it was first needed. Being nameable is not being downloadable: `availability` is what refuses the weights, and the two are independent on purpose — a licence holder DOES name this model, which is why it and each of its experts has a served_aliases entry. ABYSS IS THE ROUTED 9B, NOT THE 27B. It was going to be a single Qwen3.8-27B adapter; that model was trained (run 20260903-235101) and did not earn its cost, so the product became the four-expert mixture on Qwen3.5-9B that docs/MOE_LORA_DESIGN.md §7 and §10 specify. A routed 27B may be a later generation of this same name; it would be a new entry when it exists, because the base model is what a self-hoster loads. `lora_name` is `abyss-general` rather than a fifth name: the generalist IS the fallback for any family `experts` does not map, so pointing the two at one adapter is what makes §11’s “degrade to the generalist, never 400” true with one fewer resident LoRA slot — and slots are the binding constraint (§10, five names against `--max-loras`). prompt_version 2 and the flat 518400 band are what all four arms are training at TODAY (verified against training_runs/20260906-0142xx/run.json, min_pixels == max_pixels == 518400); CONFIRM BOTH from the run records at publish time and re-record here in the publishing commit, and if any arm has diverged give that arm its own entry — check_prompt_parity.py refuses the shared alias rather than resolving it silently."
233
+ "note": "A routed mixture of four expert LoRAs on Qwen3.5-9B — grid, pixel, video and text — selected by prompt family. Hosted only: `availability` refuses the weights, and being nameable is not being downloadable. The current client names this model against our endpoint, which is why it and each of its arms has a served_aliases entry. `lora_name` is `abyss-general` rather than a fifth name because the generalist IS the fallback for any family `experts` does not map, so the two point at one adapter and the mixture needs one fewer resident LoRA slot."
202
234
  },
203
235
  "CaptchaKraken/Abyss-27B": {
204
236
  "availability": "private",
@@ -211,7 +243,7 @@
211
243
  "min": 518400,
212
244
  "max": 518400
213
245
  },
214
- "note": "PRIVATE on the Hub. The single-adapter 27B that Abyss started as — run 20260903-235101, re-gated as 20260906-025040. NOT the routed model and not one of its arms: it is a Qwen3.8-27B, and the base model is what a self-hoster loads, so it could never share the `CaptchaKraken/Abyss` entry however similar the product story is. Kept because it is measured and it wins: Tier 2 gated_overall 0.6699 against the v1.2 baseline's 0.6456, solved 0.7113, forgetting 0.0052. What it did not win was its COST — see the `CaptchaKraken/Abyss` note — which is a statement about serving economics, not about the weights, and is exactly why the weights are archived rather than deleted. Band and generation are the run's own (min_pixels == max_pixels == 518400, PROMPT_VERSION 2), read from training_runs/20260906-025040/model.json rather than assumed."
246
+ "note": "A single-adapter Qwen3.8-27B. NOT the routed Abyss and not one of its arms — the base model is what a self-hoster loads, so it could never share that entry. Archived rather than served."
215
247
  },
216
248
  "CaptchaKraken/Abyss-grid": {
217
249
  "availability": "private",
@@ -224,7 +256,7 @@
224
256
  "min": 518400,
225
257
  "max": 518400
226
258
  },
227
- "note": "PRIVATE on the Hub. The 'grid' expert arm of the routed CaptchaKraken/Abyss, from run 20260906-014318. Tier 2 gated_overall 0.7223 against a 0.6644 baseline, solved 0.6719, forgetting 0; Tier 3 9/10 pairs. Its run reads `blocked` for a TIER 1 failure that was this repo's own test harness (a module its sys.path insertion made unclassifiable, fixed in 79201f9aa) and not the model — the tier that judges the weights passed. It has its OWN entry rather than an alias onto the router because it now has its own weights at its own repo id, and `experts` names what goes ON THE WIRE — an alias would resolve a real, separately fetchable adapter to another model's entry. It still declares the router's prompt generation and pixel band, which is what check_prompt_parity.py requires of every expert and what makes the mixture coherent."
259
+ "note": "The 'grid' expert arm of the routed CaptchaKraken/Abyss. It has its own entry rather than an alias onto the router because it has its own weights at its own repo id, and `experts` names what goes on the wire. It declares the router's prompt generation and pixel band, which is what the parity gate requires of every arm and what makes the mixture coherent."
228
260
  },
229
261
  "CaptchaKraken/Abyss-text": {
230
262
  "availability": "private",
@@ -237,19 +269,45 @@
237
269
  "min": 518400,
238
270
  "max": 518400
239
271
  },
240
- "note": "PRIVATE on the Hub. The 'text' expert arm of the routed CaptchaKraken/Abyss, from run 20260906-014253. Tier 2 gated_overall 0.8750 against a 0.8375 baseline, solved 0.8750, forgetting 0; Tier 3 5/6 pairs. `passed-partial` because the run is type-narrowed by construction — an arm trains on its own families — which disqualifies `ready-to-deploy` and says nothing bad about it. It has its OWN entry rather than an alias onto the router because it now has its own weights at its own repo id, and `experts` names what goes ON THE WIRE — an alias would resolve a real, separately fetchable adapter to another model's entry. It still declares the router's prompt generation and pixel band, which is what check_prompt_parity.py requires of every expert and what makes the mixture coherent."
272
+ "note": "The 'text' expert arm of the routed CaptchaKraken/Abyss. Own entry, own weights, and it declares the router's prompt generation and pixel band — see CaptchaKraken/Abyss-grid."
273
+ },
274
+ "CaptchaKraken/Abyss-video": {
275
+ "availability": "private",
276
+ "prompt_version": "2",
277
+ "kind": "lora",
278
+ "base_model": "Qwen/Qwen3.5-9B",
279
+ "lora_revision": "main",
280
+ "lora_name": "abyss-video",
281
+ "pixel_budget": {
282
+ "min": 518400,
283
+ "max": 518400
284
+ },
285
+ "note": "The 'video' expert arm of the routed CaptchaKraken/Abyss. Own entry, own weights, and it declares the router's prompt generation and pixel band — see CaptchaKraken/Abyss-grid."
286
+ },
287
+ "CaptchaKraken/Abyss-general": {
288
+ "availability": "private",
289
+ "prompt_version": "2",
290
+ "kind": "lora",
291
+ "base_model": "Qwen/Qwen3.5-9B",
292
+ "lora_revision": "main",
293
+ "lora_name": "abyss-general",
294
+ "pixel_budget": {
295
+ "min": 518400,
296
+ "max": 518400
297
+ },
298
+ "note": "The 'pixel' expert arm of the routed CaptchaKraken/Abyss and its generalist fallback: the router's own `lora_name` names this entry. Own entry, own weights, and it declares the router's prompt generation and pixel band — see CaptchaKraken/Abyss-grid."
241
299
  }
242
300
  },
243
301
  "served_aliases": {
244
- "_comment": "A hosted endpoint serves a bare name, not a repo id. Map it back so prompt resolution works for callers who only ever see the alias. api.captchakraken.com now serves ONE model behind the permanent public name `captcha`, whatever the caller sends, so `captcha` means whichever adapter is current there — today CaptchaKraken-Lora-v1.2 — and NOT the v1.1 weights that share the name as a local vLLM alias. Self-hosters resolve by repo id via CAPTCHA_LORA_ADAPTER and are unaffected. Move this in the same change as the gateway's CKGATE_UPSTREAM_MODEL or the hosted model answers a prompt generation it was not trained on.",
302
+ "_comment": "A hosted endpoint serves a bare name, not a repo id. Map it back so prompt resolution works for callers who only ever see the alias. The hosted API serves one model behind the permanent public name `captcha`, whatever the caller sends, so `captcha` means whichever adapter is current there. Self-hosters resolve by repo id via CAPTCHA_LORA_ADAPTER and are unaffected. Move this in the same change as the gateway's upstream model, or the hosted model answers a prompt generation it was not trained on.",
245
303
  "captcha": "CaptchaKraken/CaptchaKraken-Lora-v1.2",
246
304
  "captcha-v12": "CaptchaKraken/CaptchaKraken-Lora-v1.2",
247
- "_comment_abyss": "`abyss` is here for the OPPOSITE reason to the two above, and it is not a contradiction of `availability: licensed`. An alias says which model a served NAME means; availability says whether its weights can be downloaded. A licence holder does name this model — the hosted API accepts it from a licensed key and lists it to them on /v1/models so their SDK can — and without this line `canonical_model_id('abyss')` is None, so their prompts resolve through the `latest` fallback: the exact model/generation mispairing this file exists to prevent, silent, and only accidentally right while `latest` happens to share the generation. It does NOT make the weights fetchable; `captchakraken fetch` reads availability and refuses this name, which is also the clear error a self-hoster who typed it deserves.",
305
+ "_comment_abyss": "An alias says which model a served NAME means; `availability` says whether its weights can be downloaded. The two are independent. The current client names this model against our endpoint — the hosted API serves it to every account and lists it on /v1/models — and without this line prompt resolution falls through to the `latest` fallback, which is the model/generation mispairing this file exists to prevent. It does not make the weights fetchable.",
248
306
  "abyss": "CaptchaKraken/Abyss",
249
- "_comment_abyss_experts": "The four expert arms. These are the names that go ON THE WIRE — a routed request for a grid names `abyss-grid`, never `abyss` — so without these lines `canonical_model_id` returns None for the string the client actually sent, and prompt resolution falls through to the `latest` fallback on every routed solve. That is the mispairing `abyss` itself is here to prevent, one level down and four times over. Aliasing four arms onto one entry rather than giving each its own is deliberate and CHECKED: they share a base, a prompt generation and a pixel band today, and check_prompt_parity.py requires every expert name to resolve to a registered entry declaring the SAME prompt_version and pixel_budget as the router — so the day an arm trains at another generation or another band, the gate refuses the alias and that arm has to graduate to an entry of its own.",
307
+ "_comment_abyss_experts": "The four expert arms. These are the names that go on the wire — a routed request for a grid names `abyss-grid`, never `abyss` — so without these lines prompt resolution falls through to `latest` on every routed solve. Aliasing the arms onto one entry is deliberate and checked: they share a base, a prompt generation and a pixel band, and the parity gate requires every expert name to resolve to a registered entry declaring the SAME prompt_version and pixel_budget as the router — so the day an arm trains at another generation or band, the gate refuses the alias and that arm has to graduate to an entry of its own.",
250
308
  "abyss-grid": "CaptchaKraken/Abyss-grid",
251
- "abyss-general": "CaptchaKraken/Abyss",
252
- "abyss-video": "CaptchaKraken/Abyss",
309
+ "abyss-general": "CaptchaKraken/Abyss-general",
310
+ "abyss-video": "CaptchaKraken/Abyss-video",
253
311
  "abyss-text": "CaptchaKraken/Abyss-text",
254
312
  "abyss-27b": "CaptchaKraken/Abyss-27B"
255
313
  }