arcus-cli 0.1.0__tar.gz → 0.2.0__tar.gz

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 (57) hide show
  1. arcus_cli-0.2.0/.github/ISSUE_TEMPLATE/bug_report.yml +47 -0
  2. arcus_cli-0.2.0/.github/ISSUE_TEMPLATE/feature_request.yml +25 -0
  3. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/.github/workflows/release.yml +13 -0
  4. arcus_cli-0.2.0/CONTRIBUTING.md +48 -0
  5. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/PKG-INFO +213 -17
  6. arcus_cli-0.2.0/README.md +474 -0
  7. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/pyproject.toml +1 -1
  8. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/adapters/arc_adapter.py +45 -2
  9. arcus_cli-0.2.0/src/arcus/cli.py +1078 -0
  10. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/config.py +19 -4
  11. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/quality/gate.py +63 -9
  12. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/routing/bandit.py +22 -2
  13. arcus_cli-0.2.0/src/arcus/routing/model_catalog.py +96 -0
  14. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/routing/reward.py +9 -13
  15. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/routing/warm_start.py +9 -6
  16. arcus_cli-0.2.0/tests/adapters/test_arc_adapter.py +167 -0
  17. arcus_cli-0.2.0/tests/quality/test_gate.py +402 -0
  18. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/tests/routing/test_bandit.py +13 -2
  19. arcus_cli-0.2.0/tests/routing/test_model_catalog.py +116 -0
  20. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/tests/routing/test_reward.py +10 -15
  21. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/tests/routing/test_warm_start.py +31 -1
  22. arcus_cli-0.2.0/tests/test_cli.py +1846 -0
  23. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/tests/test_config.py +30 -0
  24. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/uv.lock +1 -1
  25. arcus_cli-0.1.0/README.md +0 -278
  26. arcus_cli-0.1.0/src/arcus/cli.py +0 -335
  27. arcus_cli-0.1.0/tests/adapters/test_arc_adapter.py +0 -70
  28. arcus_cli-0.1.0/tests/quality/test_gate.py +0 -230
  29. arcus_cli-0.1.0/tests/test_cli.py +0 -472
  30. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/.github/workflows/ci.yml +0 -0
  31. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/.gitignore +0 -0
  32. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/.python-version +0 -0
  33. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/LICENSE +0 -0
  34. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/__init__.py +0 -0
  35. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/adapters/__init__.py +0 -0
  36. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/cache/__init__.py +0 -0
  37. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/cache/benchmark.py +0 -0
  38. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/cache/semantic_cache.py +0 -0
  39. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/embeddings.py +0 -0
  40. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/eval/__init__.py +0 -0
  41. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/eval/offline.py +0 -0
  42. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/eval/regret.py +0 -0
  43. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/quality/__init__.py +0 -0
  44. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/routing/__init__.py +0 -0
  45. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/routing/context.py +0 -0
  46. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/storage/__init__.py +0 -0
  47. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/storage/db.py +0 -0
  48. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/src/arcus/storage/stats.py +0 -0
  49. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/tests/adapters/test_arc_adapter_live.py +0 -0
  50. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/tests/cache/test_benchmark.py +0 -0
  51. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/tests/cache/test_semantic_cache.py +0 -0
  52. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/tests/eval/test_offline.py +0 -0
  53. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/tests/eval/test_regret.py +0 -0
  54. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/tests/routing/test_context.py +0 -0
  55. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/tests/storage/test_db.py +0 -0
  56. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/tests/storage/test_stats.py +0 -0
  57. {arcus_cli-0.1.0 → arcus_cli-0.2.0}/tests/test_embeddings.py +0 -0
@@ -0,0 +1,47 @@
1
+ name: Bug report
2
+ description: Something in arcus isn't working as expected
3
+ labels: ["bug"]
4
+ body:
5
+ - type: textarea
6
+ id: what-happened
7
+ attributes:
8
+ label: What happened
9
+ description: What did you run, and what went wrong?
10
+ placeholder: |
11
+ Ran `arcus "..."` and got a traceback / wrong answer / hang, etc.
12
+ validations:
13
+ required: true
14
+
15
+ - type: textarea
16
+ id: expected
17
+ attributes:
18
+ label: What you expected instead
19
+ validations:
20
+ required: true
21
+
22
+ - type: input
23
+ id: version
24
+ attributes:
25
+ label: arcus-cli version
26
+ description: Output of `pip show arcus-cli` or `uv tool list`
27
+ validations:
28
+ required: true
29
+
30
+ - type: dropdown
31
+ id: mode
32
+ attributes:
33
+ label: Which mode were you using?
34
+ options:
35
+ - "arcus \"...\" (one-shot)"
36
+ - "arcus chat"
37
+ - "arcus stats"
38
+ - "arcus --random"
39
+ - Not sure / other
40
+ validations:
41
+ required: true
42
+
43
+ - type: textarea
44
+ id: logs
45
+ attributes:
46
+ label: Full error output, if any
47
+ render: shell
@@ -0,0 +1,25 @@
1
+ name: Feature request
2
+ description: Suggest something arcus should do
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: textarea
6
+ id: problem
7
+ attributes:
8
+ label: What problem does this solve?
9
+ description: What are you trying to do that arcus doesn't support today?
10
+ validations:
11
+ required: true
12
+
13
+ - type: textarea
14
+ id: proposal
15
+ attributes:
16
+ label: What you'd like to see
17
+ description: A rough idea of the behavior or interface is fine, doesn't need to be a full spec.
18
+ validations:
19
+ required: true
20
+
21
+ - type: textarea
22
+ id: alternatives
23
+ attributes:
24
+ label: Alternatives you've considered
25
+ description: Any workarounds you're using today, or other approaches you thought about.
@@ -48,3 +48,16 @@ jobs:
48
48
 
49
49
  - name: Publish to PyPI
50
50
  uses: pypa/gh-action-pypi-publish@release/v1
51
+
52
+ github_release:
53
+ needs: publish
54
+ runs-on: ubuntu-latest
55
+ permissions:
56
+ contents: write # required to create the GitHub Release
57
+ steps:
58
+ - uses: actions/checkout@v4
59
+
60
+ - name: Create GitHub Release
61
+ uses: softprops/action-gh-release@v2
62
+ with:
63
+ generate_release_notes: true
@@ -0,0 +1,48 @@
1
+ # Contributing to Arcus
2
+
3
+ ## Setup
4
+
5
+ ```bash
6
+ git clone https://github.com/ShakirFarhan/Arcus.git
7
+ cd Arcus
8
+ uv sync
9
+ ```
10
+
11
+ You'll need an ARC API key to run the tool live (`llm.arc.vt.edu` > User
12
+ profile > Settings > Account > API keys). Most of the test suite runs
13
+ without one; a handful of live tests in `tests/adapters/test_arc_adapter_live.py`
14
+ only run if `ARC_API_KEY` is set in your shell:
15
+
16
+ ```bash
17
+ uv run pytest tests/ -q
18
+ ```
19
+
20
+ ## Before opening a PR
21
+
22
+ - Add a test for any new business logic (routing, reward calculation,
23
+ cache scoring, quality checks). Bug fixes should include a test that
24
+ fails without the fix.
25
+ - Keep PRs focused, one change per PR rather than bundling unrelated
26
+ fixes together.
27
+ - Run the full test suite locally before pushing; CI runs it again on
28
+ every PR but catching it early saves a round trip.
29
+ - Algorithmic functions (bandit logic, reward math, cache scoring)
30
+ should have a docstring explaining *why* a design choice was made,
31
+ not just what the code does.
32
+
33
+ ## Code style
34
+
35
+ - No new dependencies without discussing them in the PR description
36
+ first, this project deliberately keeps its dependency footprint
37
+ small.
38
+ - Comments should explain non-obvious reasoning (a workaround, a
39
+ constraint, a subtle invariant), not restate what the code already
40
+ says.
41
+ - Prefer editing existing files and reusing existing patterns over
42
+ introducing new abstractions.
43
+
44
+ ## Reporting bugs
45
+
46
+ Open an issue with what you ran, what you expected, and what actually
47
+ happened. If it's reproducible with `arcus --random`, mention that too,
48
+ it helps narrow down whether it's routing-specific.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: arcus-cli
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Adaptive routing, a quality gate, and a correctness-aware cache on top of Virginia Tech ARC's LLM API
5
5
  Project-URL: Homepage, https://github.com/ShakirFarhan/Arcus
6
6
  Project-URL: Repository, https://github.com/ShakirFarhan/Arcus
@@ -30,6 +30,11 @@ Description-Content-Type: text/markdown
30
30
 
31
31
  # Arcus
32
32
 
33
+ [![CI](https://github.com/ShakirFarhan/Arcus/actions/workflows/ci.yml/badge.svg)](https://github.com/ShakirFarhan/Arcus/actions/workflows/ci.yml)
34
+ [![PyPI](https://img.shields.io/pypi/v/arcus-cli.svg)](https://pypi.org/project/arcus-cli/)
35
+ [![Python](https://img.shields.io/pypi/pyversions/arcus-cli.svg)](https://pypi.org/project/arcus-cli/)
36
+ [![License: MIT](https://img.shields.io/badge/license-MIT-yellow.svg)](LICENSE)
37
+
33
38
  A CLI that sits on top of Virginia Tech ARC's LLM API and makes it
34
39
  smarter: it picks which of ARC's four open-weight models to route a
35
40
  request to, checks the response before handing it back to you, and
@@ -40,11 +45,17 @@ machine with your own ARC key. Nothing goes through a shared server.
40
45
  arcus "explain how binary search works"
41
46
  ```
42
47
 
48
+ **Contents:** [Why](#why) · [How it works](#how-it-works) · [Install](#install)
49
+ · [Usage](#usage) · [Status](#status) · [Security & privacy](#security--privacy)
50
+
43
51
  ## Why
44
52
 
45
53
  ARC gives every VT user free access to four open-weight models
46
54
  (gpt-oss-120b, GLM-5.3, Kimi-K3, DeepSeek-V4-Flash) through one
47
- OpenAI-compatible endpoint. Picking a model by hand every time is
55
+ OpenAI-compatible endpoint ([ARC's own docs](https://www.docs.arc.vt.edu/ai/011_llm_api_arc_vt_edu.html)
56
+ cover the service itself, including its rate limits and data-handling
57
+ approval, arcus is a client built on top of it, not affiliated with
58
+ ARC). Picking a model by hand every time is
48
59
  tedious, and a plain HTTP 200 doesn't tell you whether the response
49
60
  inside it was actually any good, a truncated answer or a flat refusal
50
61
  comes back looking the same as a correct one unless something reads the
@@ -127,6 +138,26 @@ by replaying the local request log, which works because a bandit's
127
138
  `update()` is just an associative accumulation of pull counts and reward
128
139
  sums.
129
140
 
141
+ The four model ids arcus routes to live in `ArcModel`, but ARC runs its
142
+ own model catalog independently and can rename or retire an entry at
143
+ any time. `src/arcus/routing/model_catalog.py` checks the configured
144
+ list against what ARC is actually serving (cached for a few hours so
145
+ this doesn't cost a network round trip on every call) and quietly drops
146
+ anything that's no longer live, rather than routing to a model
147
+ guaranteed to fail. Local history logged under a since-renamed model id
148
+ is skipped the same way when the bandit's state gets rebuilt.
149
+
150
+ Optionally, code, math, and long-document questions can also route
151
+ across ARC's `-thinking-*` reasoning-effort model variants, not just
152
+ the base four (`arcus config set enable_reasoning_variants true`,
153
+ default off). Everyday questions stay on the fast base four either way.
154
+ **This is built and unit tested but hasn't been run against a real ARC
155
+ key from this environment**, ARC's docs list these as separate catalog
156
+ model ids rather than a parameter on the base id, the same pattern
157
+ already confirmed for web search's legacy-tool-calling variants below,
158
+ but that specific assumption is unverified. Ask a code or math question
159
+ after turning it on and confirm it actually answers before trusting it.
160
+
130
161
  ### Quality gate
131
162
 
132
163
  Five independent checks run over every response: truncation
@@ -137,6 +168,17 @@ negative reward for that model in that context and retries with a
137
168
  different one, up to once per available arm, before giving up and
138
169
  returning the last attempt. See `src/arcus/quality/gate.py`.
139
170
 
171
+ ARC caps concurrent requests per account rather than per model, so a
172
+ 429 doesn't mean the model that was just called is bad, switching to a
173
+ different arm wouldn't help either. A rate limit gets a few short
174
+ retries against the same model before it's treated as a real failure,
175
+ so one busy moment doesn't unfairly tank that model's learned reward.
176
+
177
+ Similarly, ARC's access restriction (see Install below) applies to the
178
+ whole account, not one model, so hitting it stops the request
179
+ immediately with a clear message instead of cycling through every arm
180
+ against the same wall, and doesn't count against any model's reward.
181
+
140
182
  ### Semantic cache
141
183
 
142
184
  Local `sentence-transformers` embeddings (`all-MiniLM-L6-v2`), cosine
@@ -202,6 +244,29 @@ All three real algorithms land well below the random baseline, which is
202
244
  the actual point: they're spending far less time on worse-than-best
203
245
  arms than picking blindly would.
204
246
 
247
+ ### Document Q&A and web search
248
+
249
+ Both build directly on capabilities ARC's own API already provides,
250
+ rather than reimplementing them:
251
+
252
+ - **`arcus --doc <path> "question"`** uploads the file to ARC's RAG
253
+ endpoint, attaches it to the request, and deletes it from your ARC
254
+ account again once you have an answer. Works across all four core
255
+ models, confirmed live against the real API.
256
+ - **`arcus --web "question"`** routes to ARC's `server:websearch` tool
257
+ through three of its "legacy-tool-calling" model variants
258
+ (`gpt-oss-120b`, `Kimi-K3`, and the older `glm-52` variant) confirmed
259
+ to actually perform a real search and cite sources. A fourth,
260
+ DeepSeek's legacy variant, accepts the same request without erroring
261
+ but doesn't reliably act on it, live testing caught it answering a
262
+ time-sensitive question wrong with no citation, so it's left out.
263
+
264
+ Both skip the semantic cache: a cached answer keyed on question text
265
+ alone would risk answering about the wrong document, or serving a
266
+ web-search answer that's since gone stale. See `src/arcus/cli.py`
267
+ (`run_doc_ask`, `run_web_ask`) and `ArcAdapter.upload_file`/
268
+ `delete_file` in `src/arcus/adapters/arc_adapter.py`.
269
+
205
270
  ## Install
206
271
 
207
272
  ```bash
@@ -225,6 +290,23 @@ First run walks you through a one-time setup: it asks for your ARC key
225
290
  `~/.config/arcus/config.toml` with `chmod 600`. No separate setup
226
291
  command to remember.
227
292
 
293
+ ARC restricts the API to VT's campus network, so this (and every
294
+ `arcus` call after it) needs either an on-campus connection or VT's
295
+ VPN. Arcus surfaces this as a clear message rather than the generic
296
+ "no usable response" error when it happens.
297
+
298
+ For tab completion on the `chat`/`stats`/`eval`/`models`/`config`/
299
+ `--random`/`--model`/`--image`/`--doc`/`--web` words, add one of these
300
+ to your shell config:
301
+
302
+ ```bash
303
+ # zsh, in ~/.zshrc
304
+ eval "$(arcus --completion zsh)"
305
+
306
+ # bash, in ~/.bashrc
307
+ eval "$(arcus --completion bash)"
308
+ ```
309
+
228
310
  ## Usage
229
311
 
230
312
  ```bash
@@ -240,39 +322,145 @@ python broken.py 2>&1 | arcus "why is this failing"
240
322
  # force the random-routing baseline instead of the learned bandit policy
241
323
  arcus --random "explain how binary search works"
242
324
 
325
+ # skip the bandit entirely and pin a specific model for this one call
326
+ arcus --model GLM-5.3 "explain how binary search works"
327
+
243
328
  # see how it's doing
244
329
  arcus stats
245
330
 
331
+ # compare the routing policy actually run against offline alternatives
332
+ arcus eval
333
+
334
+ # see every model ARC is currently serving, and which ones arcus routes to
335
+ arcus models
336
+
246
337
  # hold a multi-turn conversation instead of a single question
247
338
  arcus chat
339
+
340
+ # inside chat, --doc/--web/--image/--model all work inline, one
341
+ # attachment per turn: "you: --doc paper.pdf summarize this"
342
+
343
+ # save the conversation to a file when you leave
344
+ arcus chat --save transcript.md
345
+
346
+ # ask about an image (routes to Kimi-K3, the one ARC model documented
347
+ # as vision-capable)
348
+ arcus --image screenshot.png "what's wrong with this code?"
349
+
350
+ # ask a question about a document, ARC handles the retrieval
351
+ arcus --doc syllabus.pdf "when is the midterm?"
352
+
353
+ # ask something that needs current information
354
+ arcus --web "what's the latest release of Python?"
355
+
356
+ # view or change local settings
357
+ arcus config
358
+ arcus config set bandit_algorithm ucb1
359
+
360
+ # check which version is installed
361
+ arcus --version
248
362
  ```
249
363
 
364
+ Quick reference, details for each are below:
365
+
366
+ | Command | What it does |
367
+ | --- | --- |
368
+ | `arcus "<question>"` | Ask something, routed through the bandit + quality gate. |
369
+ | `arcus --random "<question>"` | Same, but routes randomly instead of using the learned policy. |
370
+ | `arcus --model NAME "<question>"` | Skip routing, pin one specific model. |
371
+ | `arcus --image PATH "<question>"` | Ask about an image (vision-capable model only). |
372
+ | `arcus --doc PATH "<question>"` | Ask about an uploaded document (RAG). |
373
+ | `arcus --web "<question>"` | Ask something needing current information (web search). |
374
+ | `arcus chat [--save PATH]` | Multi-turn conversation; `--doc`/`--web`/`--image`/`--model` all work inline per turn. |
375
+ | `arcus stats` | Local routing performance so far. |
376
+ | `arcus eval` | Offline comparison of the routing policy against alternatives. |
377
+ | `arcus models` | ARC's live model catalog vs. what arcus routes to. |
378
+ | `arcus config [set ...]` | View or change local settings. |
379
+ | `arcus --version` | Installed version. |
380
+
250
381
  `arcus chat` opens a REPL that remembers everything said earlier in that
251
382
  session (resending the growing transcript each turn, since ARC's API has
252
383
  no session concept of its own) and routes each turn through the same
253
384
  bandit/quality-gate/logging pipeline as a one-shot `arcus "..."` call.
254
385
  Type `exit` or press ctrl-d to leave. The conversation only lives for
255
- that one run, closing the terminal loses it.
386
+ that one run, closing the terminal loses it, unless you pass `--save
387
+ <path>`, which writes the full transcript (not just whatever's still in
388
+ the trimmed context window) to a markdown file when you exit.
389
+
390
+ `--doc PATH`, `--web`, `--image PATH`, and `--model NAME` all work
391
+ inline inside `arcus chat` too, typed as part of a turn (`you: --doc
392
+ paper.pdf summarize this`), one attachment per turn, the same rules as
393
+ below apply. The attachment only applies to that one turn, a later turn
394
+ that wants to keep asking about the same document attaches it again.
395
+
396
+ `arcus --image <path> "question"` attaches an image to a one-shot
397
+ question. It always goes to Kimi-K3 rather than through the usual
398
+ bandit comparison, confirmed directly against the API to be the only
399
+ one of the four models that can actually see an image, GLM-5.3 and
400
+ DeepSeek-V4-Flash both reject image content outright and gpt-oss-120b
401
+ accepts the request but reports it can't see anything. Skips the
402
+ semantic cache entirely too, matching on the question text alone would
403
+ risk serving back an answer about a completely different image.
404
+
405
+ `arcus --doc <path> "question"` and `arcus --web "question"` work the
406
+ same way as `--image`, cache skipped, see "Document Q&A and web search"
407
+ above for what each actually does. Only one of `--image`, `--doc`, or
408
+ `--web` can be used at a time.
409
+
410
+ `arcus --model NAME "question"` skips the bandit entirely and always
411
+ uses that model, checked against ARC's live catalog first. Since
412
+ there's only one arm, the quality gate's checks (empty, truncated,
413
+ repetitive, refusal) still run and still get reported, there's just no
414
+ other model left to fall back to if it fails, that's the point of an
415
+ explicit override. Combine with `--web` or `--image` and the name has
416
+ to be one of the models valid for that mode.
417
+
418
+ `arcus config` shows your current settings (the API key masked) and the
419
+ path to the config file. `arcus config set bandit_algorithm <algo>`
420
+ changes which bandit algorithm arcus uses without hand-editing the TOML
421
+ file. `arcus config set enable_reasoning_variants <true|false>` turns
422
+ the reasoning-effort routing described above on or off. Re-keying isn't
423
+ supported here on purpose, delete the config file and run `arcus` again
424
+ to go through setup fresh.
256
425
 
257
426
  `arcus stats` reads your local SQLite log and prints a `rich`-formatted
258
427
  table: request count, average reward, average latency, and cost score
259
428
  per model per mode, plus your cache hit rate and how many attempts the
260
429
  quality gate has caught and retried. Entirely local, no network call.
261
430
 
431
+ `arcus eval` runs the offline policy evaluation described above against
432
+ your own logged history and prints the comparison table (IPS and
433
+ doubly-robust estimates with 95% confidence intervals for the greedy
434
+ policy and each "always use model X" baseline, against what actually
435
+ ran). Below 30 logged bandit-mode requests it still prints the table but
436
+ flags the numbers as illustrative only, a bootstrap confidence interval
437
+ on a handful of rows isn't a reliable comparison yet.
438
+
262
439
  ## Status
263
440
 
264
441
  Everything described above is implemented and working: the ARC adapter,
265
442
  context classification, all three bandit algorithms with propensity
266
- tracking, the reward function, the quality gate, the semantic cache and
267
- its benchmark, the CLI (ask command, chat mode, first-run wizard, error
268
- piping, stats), and the offline evaluation + regret benchmarking layer.
443
+ tracking, the reward function, the quality gate (including rate-limit
444
+ backoff and VPN-restriction handling), the semantic cache and its
445
+ benchmark, the CLI (ask command with a manual `--model` override, chat
446
+ mode with inline attachments and transcript export, image input,
447
+ document Q&A, web search, a config command, first-run wizard, error
448
+ piping, stats, models, and offline eval), and the offline evaluation +
449
+ regret benchmarking layer.
269
450
 
270
451
  Verified live against a real ARC key: all four models respond correctly
271
- (`tests/adapters/test_arc_adapter_live.py`), and a full end-to-end
452
+ (`tests/adapters/test_arc_adapter_live.py`), a full end-to-end
272
453
  `arcus "..."` run exercises the whole pipeline (context classification,
273
454
  cache miss, bandit routing, a real ARC call, the quality gate, logging,
274
- and caching the result) against real traffic. Test suite: 178 passing
275
- with a key set (174 plus 4 live-only tests), 4 skipped without one.
455
+ and caching the result) against real traffic, and image input, document
456
+ Q&A, and web search have each been run against real responses too, not
457
+ just unit tested. Test suite: 273 passing with a key set (269 plus 4
458
+ live-only tests), 4 skipped without one.
459
+
460
+ The one exception: reasoning-effort variant routing
461
+ (`enable_reasoning_variants`) is unit tested against a fake adapter only,
462
+ not yet confirmed against a real ARC key, and defaults off for exactly
463
+ that reason. See "Adaptive routing" above.
276
464
 
277
465
  Worth knowing: ARC's models are reasoning models under the hood, they
278
466
  write to a hidden `reasoning` field before `content`, so a small
@@ -284,19 +472,27 @@ budget of your own.
284
472
 
285
473
  What's still open:
286
474
 
287
- - Not published to PyPI yet.
288
- - Real logged usage is still thin (a handful of manual runs). Once
289
- there's a real query history, `arcus/eval/offline.py`'s
290
- `evaluate_policies()` is what turns it into the comparison table
291
- described above.
475
+ - Real logged usage is still thin (a handful of manual runs). `arcus
476
+ eval` runs the comparison today, it's just not resting on enough
477
+ data yet to trust the numbers, it says so when that's the case rather
478
+ than presenting a false-confidence table.
479
+ - Reasoning-effort variant routing needs a live-key run to confirm ARC
480
+ actually serves the `-thinking-*` ids the way its docs describe,
481
+ before it's safe to turn on by default.
292
482
 
293
483
  ## Security & privacy
294
484
 
295
485
  - Each install uses its own user's ARC key. Keys are never shared,
296
486
  bundled, or sent anywhere but ARC's own endpoint.
297
- - No data leaves your machine by default. Request logs, cache entries,
298
- and stats are all local SQLite, nothing is aggregated or reported
299
- anywhere.
487
+ - No data leaves your machine except to ARC itself, with your own key.
488
+ Request logs, cache entries, and stats are all local SQLite, nothing
489
+ is aggregated or reported anywhere else.
490
+ - `arcus --doc` uploads the whole file to your ARC account temporarily
491
+ (deleted again once you have an answer), and `arcus --web` sends your
492
+ question through ARC's own web search tool. Both stay within ARC,
493
+ same as every other request, but a document leaving your machine
494
+ entirely (even briefly, even to your own account) is worth knowing
495
+ about explicitly.
300
496
  - This tool hasn't been through ARC's security review for regulated
301
497
  data (FERPA records, health data, etc.) the way ARC's own web
302
498
  interface has. Don't route sensitive regulated data through it.