switchboard-local 0.1.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 (145) hide show
  1. switchboard_local-0.1.0/LICENSE +21 -0
  2. switchboard_local-0.1.0/PKG-INFO +270 -0
  3. switchboard_local-0.1.0/README.md +231 -0
  4. switchboard_local-0.1.0/pyproject.toml +85 -0
  5. switchboard_local-0.1.0/setup.cfg +4 -0
  6. switchboard_local-0.1.0/switchboard/__init__.py +8 -0
  7. switchboard_local-0.1.0/switchboard/app/__init__.py +1 -0
  8. switchboard_local-0.1.0/switchboard/app/api/__init__.py +1 -0
  9. switchboard_local-0.1.0/switchboard/app/api/admin.py +54 -0
  10. switchboard_local-0.1.0/switchboard/app/api/chat.py +22 -0
  11. switchboard_local-0.1.0/switchboard/app/api/health.py +18 -0
  12. switchboard_local-0.1.0/switchboard/app/api/personal.py +113 -0
  13. switchboard_local-0.1.0/switchboard/app/api/ui.py +387 -0
  14. switchboard_local-0.1.0/switchboard/app/backends/__init__.py +12 -0
  15. switchboard_local-0.1.0/switchboard/app/backends/base.py +27 -0
  16. switchboard_local-0.1.0/switchboard/app/backends/cli_agents.py +224 -0
  17. switchboard_local-0.1.0/switchboard/app/backends/ollama_backend.py +124 -0
  18. switchboard_local-0.1.0/switchboard/app/backends/registry.py +61 -0
  19. switchboard_local-0.1.0/switchboard/app/core/__init__.py +1 -0
  20. switchboard_local-0.1.0/switchboard/app/core/config.py +82 -0
  21. switchboard_local-0.1.0/switchboard/app/core/errors.py +28 -0
  22. switchboard_local-0.1.0/switchboard/app/core/logging.py +10 -0
  23. switchboard_local-0.1.0/switchboard/app/main.py +66 -0
  24. switchboard_local-0.1.0/switchboard/app/models/__init__.py +1 -0
  25. switchboard_local-0.1.0/switchboard/app/models/api.py +48 -0
  26. switchboard_local-0.1.0/switchboard/app/models/backends.py +80 -0
  27. switchboard_local-0.1.0/switchboard/app/models/capabilities.py +67 -0
  28. switchboard_local-0.1.0/switchboard/app/models/catalogue.py +107 -0
  29. switchboard_local-0.1.0/switchboard/app/models/internal.py +98 -0
  30. switchboard_local-0.1.0/switchboard/app/models/personal.py +303 -0
  31. switchboard_local-0.1.0/switchboard/app/models/policy.py +72 -0
  32. switchboard_local-0.1.0/switchboard/app/models/sessions.py +50 -0
  33. switchboard_local-0.1.0/switchboard/app/models/telemetry.py +271 -0
  34. switchboard_local-0.1.0/switchboard/app/providers/__init__.py +1 -0
  35. switchboard_local-0.1.0/switchboard/app/providers/anthropic_provider.py +66 -0
  36. switchboard_local-0.1.0/switchboard/app/providers/base.py +29 -0
  37. switchboard_local-0.1.0/switchboard/app/providers/lmstudio.py +57 -0
  38. switchboard_local-0.1.0/switchboard/app/providers/manual.py +27 -0
  39. switchboard_local-0.1.0/switchboard/app/providers/mock.py +37 -0
  40. switchboard_local-0.1.0/switchboard/app/providers/ollama.py +49 -0
  41. switchboard_local-0.1.0/switchboard/app/providers/openai_provider.py +54 -0
  42. switchboard_local-0.1.0/switchboard/app/providers/registry.py +38 -0
  43. switchboard_local-0.1.0/switchboard/app/services/__init__.py +1 -0
  44. switchboard_local-0.1.0/switchboard/app/services/answer_quality.py +284 -0
  45. switchboard_local-0.1.0/switchboard/app/services/capabilities.py +451 -0
  46. switchboard_local-0.1.0/switchboard/app/services/chat_completion.py +140 -0
  47. switchboard_local-0.1.0/switchboard/app/services/classifier.py +733 -0
  48. switchboard_local-0.1.0/switchboard/app/services/compression_layer.py +96 -0
  49. switchboard_local-0.1.0/switchboard/app/services/container.py +86 -0
  50. switchboard_local-0.1.0/switchboard/app/services/context_compression.py +149 -0
  51. switchboard_local-0.1.0/switchboard/app/services/core_factory.py +166 -0
  52. switchboard_local-0.1.0/switchboard/app/services/cost.py +36 -0
  53. switchboard_local-0.1.0/switchboard/app/services/deterministic_tools.py +284 -0
  54. switchboard_local-0.1.0/switchboard/app/services/finance_providers.py +230 -0
  55. switchboard_local-0.1.0/switchboard/app/services/finance_tool.py +217 -0
  56. switchboard_local-0.1.0/switchboard/app/services/learned_router.py +211 -0
  57. switchboard_local-0.1.0/switchboard/app/services/llm_router.py +175 -0
  58. switchboard_local-0.1.0/switchboard/app/services/local_runtime.py +165 -0
  59. switchboard_local-0.1.0/switchboard/app/services/news_tool.py +218 -0
  60. switchboard_local-0.1.0/switchboard/app/services/personal_switchboard.py +1338 -0
  61. switchboard_local-0.1.0/switchboard/app/services/policy_engine.py +109 -0
  62. switchboard_local-0.1.0/switchboard/app/services/provider_status.py +20 -0
  63. switchboard_local-0.1.0/switchboard/app/services/response_sanitizer.py +133 -0
  64. switchboard_local-0.1.0/switchboard/app/services/router.py +224 -0
  65. switchboard_local-0.1.0/switchboard/app/services/runtime_context.py +70 -0
  66. switchboard_local-0.1.0/switchboard/app/services/semantic_memory.py +240 -0
  67. switchboard_local-0.1.0/switchboard/app/services/sensitivity_escalator.py +128 -0
  68. switchboard_local-0.1.0/switchboard/app/services/session_context.py +199 -0
  69. switchboard_local-0.1.0/switchboard/app/services/status_intents.py +56 -0
  70. switchboard_local-0.1.0/switchboard/app/services/switchboard_core.py +1301 -0
  71. switchboard_local-0.1.0/switchboard/app/services/telemetry.py +80 -0
  72. switchboard_local-0.1.0/switchboard/app/services/tool_dispatcher.py +170 -0
  73. switchboard_local-0.1.0/switchboard/app/services/tools.py +319 -0
  74. switchboard_local-0.1.0/switchboard/app/services/web_search_providers.py +93 -0
  75. switchboard_local-0.1.0/switchboard/app/services/web_search_tool.py +144 -0
  76. switchboard_local-0.1.0/switchboard/app/storage/__init__.py +1 -0
  77. switchboard_local-0.1.0/switchboard/app/storage/db.py +71 -0
  78. switchboard_local-0.1.0/switchboard/app/storage/repositories.py +669 -0
  79. switchboard_local-0.1.0/switchboard/app/utils/__init__.py +1 -0
  80. switchboard_local-0.1.0/switchboard/app/utils/ids.py +7 -0
  81. switchboard_local-0.1.0/switchboard/app/utils/redaction.py +37 -0
  82. switchboard_local-0.1.0/switchboard/app/utils/secret_patterns.py +95 -0
  83. switchboard_local-0.1.0/switchboard/app/utils/time.py +11 -0
  84. switchboard_local-0.1.0/switchboard/cli.py +1484 -0
  85. switchboard_local-0.1.0/switchboard/config/__init__.py +1 -0
  86. switchboard_local-0.1.0/switchboard/config/models.yaml +295 -0
  87. switchboard_local-0.1.0/switchboard/config/personal.example.yaml +117 -0
  88. switchboard_local-0.1.0/switchboard/config/personal.yaml +117 -0
  89. switchboard_local-0.1.0/switchboard/config/policies.yaml +49 -0
  90. switchboard_local-0.1.0/switchboard/config/router_weights.json +4645 -0
  91. switchboard_local-0.1.0/switchboard/config/sensitivity_weights.json +3101 -0
  92. switchboard_local-0.1.0/switchboard/config/tool_dispatcher_weights.json +7733 -0
  93. switchboard_local-0.1.0/switchboard/evals/__init__.py +12 -0
  94. switchboard_local-0.1.0/switchboard/evals/datasets.py +864 -0
  95. switchboard_local-0.1.0/switchboard/evals/mock_adapters.py +129 -0
  96. switchboard_local-0.1.0/switchboard/evals/quality_bench.py +501 -0
  97. switchboard_local-0.1.0/switchboard/evals/quality_dataset.py +1776 -0
  98. switchboard_local-0.1.0/switchboard/evals/real_providers.py +183 -0
  99. switchboard_local-0.1.0/switchboard/evals/real_smoke.py +473 -0
  100. switchboard_local-0.1.0/switchboard/evals/reports.py +146 -0
  101. switchboard_local-0.1.0/switchboard/evals/runner.py +374 -0
  102. switchboard_local-0.1.0/switchboard/evals/scorers.py +47 -0
  103. switchboard_local-0.1.0/switchboard/evals/types.py +155 -0
  104. switchboard_local-0.1.0/switchboard/training/__init__.py +1 -0
  105. switchboard_local-0.1.0/switchboard/training/augment.py +104 -0
  106. switchboard_local-0.1.0/switchboard/training/external_datasets.py +253 -0
  107. switchboard_local-0.1.0/switchboard/training/feedback_loop.py +458 -0
  108. switchboard_local-0.1.0/switchboard/training/router_dataset.py +480 -0
  109. switchboard_local-0.1.0/switchboard/training/sensitivity_dataset.py +166 -0
  110. switchboard_local-0.1.0/switchboard/training/tool_dispatcher_dataset.py +224 -0
  111. switchboard_local-0.1.0/switchboard/training/train_router.py +258 -0
  112. switchboard_local-0.1.0/switchboard_local.egg-info/PKG-INFO +270 -0
  113. switchboard_local-0.1.0/switchboard_local.egg-info/SOURCES.txt +143 -0
  114. switchboard_local-0.1.0/switchboard_local.egg-info/dependency_links.txt +1 -0
  115. switchboard_local-0.1.0/switchboard_local.egg-info/entry_points.txt +3 -0
  116. switchboard_local-0.1.0/switchboard_local.egg-info/requires.txt +18 -0
  117. switchboard_local-0.1.0/switchboard_local.egg-info/top_level.txt +1 -0
  118. switchboard_local-0.1.0/tests/test_architecture_fixes.py +403 -0
  119. switchboard_local-0.1.0/tests/test_capability_strength.py +344 -0
  120. switchboard_local-0.1.0/tests/test_chat_api.py +211 -0
  121. switchboard_local-0.1.0/tests/test_classifier.py +254 -0
  122. switchboard_local-0.1.0/tests/test_config_paths.py +100 -0
  123. switchboard_local-0.1.0/tests/test_context_compression.py +207 -0
  124. switchboard_local-0.1.0/tests/test_cost.py +37 -0
  125. switchboard_local-0.1.0/tests/test_dogfood_regressions.py +819 -0
  126. switchboard_local-0.1.0/tests/test_evals.py +468 -0
  127. switchboard_local-0.1.0/tests/test_external_datasets.py +180 -0
  128. switchboard_local-0.1.0/tests/test_feedback_loop.py +419 -0
  129. switchboard_local-0.1.0/tests/test_learned_router.py +275 -0
  130. switchboard_local-0.1.0/tests/test_live_tools.py +294 -0
  131. switchboard_local-0.1.0/tests/test_local_runtime.py +78 -0
  132. switchboard_local-0.1.0/tests/test_paper_components.py +437 -0
  133. switchboard_local-0.1.0/tests/test_personal_api.py +450 -0
  134. switchboard_local-0.1.0/tests/test_personal_daily_use.py +1167 -0
  135. switchboard_local-0.1.0/tests/test_personal_providers.py +99 -0
  136. switchboard_local-0.1.0/tests/test_policy_engine.py +80 -0
  137. switchboard_local-0.1.0/tests/test_provider_mock.py +39 -0
  138. switchboard_local-0.1.0/tests/test_quality_bench.py +146 -0
  139. switchboard_local-0.1.0/tests/test_router.py +132 -0
  140. switchboard_local-0.1.0/tests/test_session_context.py +694 -0
  141. switchboard_local-0.1.0/tests/test_switchboard_capabilities.py +966 -0
  142. switchboard_local-0.1.0/tests/test_switchboard_core.py +1142 -0
  143. switchboard_local-0.1.0/tests/test_tool_dispatcher.py +407 -0
  144. switchboard_local-0.1.0/tests/test_ui_api.py +492 -0
  145. switchboard_local-0.1.0/tests/test_ui_v2.py +169 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Vinay Gupta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,270 @@
1
+ Metadata-Version: 2.4
2
+ Name: switchboard-local
3
+ Version: 0.1.0
4
+ Summary: Privacy-aware, local-first router across CLI coding agents (Codex, Claude Code) and local LLMs (Ollama).
5
+ Author-email: Vinay Gupta <ai.vinaygupta@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/aivinay/switchboard
8
+ Project-URL: Repository, https://github.com/aivinay/switchboard
9
+ Project-URL: Issues, https://github.com/aivinay/switchboard/issues
10
+ Keywords: llm,llm-routing,local-first,privacy,ollama,claude-code,codex,ai-agents,orchestration,on-device
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.11
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: fastapi>=0.115.0
24
+ Requires-Dist: httpx>=0.27.0
25
+ Requires-Dist: pydantic>=2.8.0
26
+ Requires-Dist: pydantic-settings>=2.4.0
27
+ Requires-Dist: pyyaml>=6.0.2
28
+ Requires-Dist: sqlmodel>=0.0.22
29
+ Requires-Dist: uvicorn[standard]>=0.30.0
30
+ Provides-Extra: dev
31
+ Requires-Dist: mypy>=1.11.0; extra == "dev"
32
+ Requires-Dist: pytest>=8.3.0; extra == "dev"
33
+ Requires-Dist: ruff>=0.6.0; extra == "dev"
34
+ Provides-Extra: finance
35
+ Requires-Dist: yfinance>=0.2.50; extra == "finance"
36
+ Provides-Extra: router
37
+ Requires-Dist: numpy>=1.26; extra == "router"
38
+ Dynamic: license-file
39
+
40
+ <h1 align="center">Switchboard</h1>
41
+
42
+ <p align="center"><strong>A privacy-aware, local-first router across your CLI coding agents and local LLMs.</strong></p>
43
+
44
+ <p align="center">
45
+ <a href="https://github.com/aivinay/switchboard/actions/workflows/ci.yml"><img src="https://github.com/aivinay/switchboard/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
46
+ <a href="RELEASE.md"><img src="https://img.shields.io/badge/PyPI-pending-lightgrey.svg" alt="PyPI: pending"></a>
47
+ <img src="https://img.shields.io/badge/python-3.11%2B-blue.svg" alt="Python 3.11+">
48
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License: MIT"></a>
49
+ <a href="https://doi.org/10.5281/zenodo.20789935"><img src="https://img.shields.io/badge/DOI-10.5281%2Fzenodo.20789935-blue.svg" alt="DOI"></a>
50
+ </p>
51
+
52
+ <p align="center">
53
+ <a href="#get-started-60-seconds">Install</a> ·
54
+ <a href="#how-it-works">How it works</a> ·
55
+ <a href="#context-memory-and-tokens">Context</a> ·
56
+ <a href="#proof">Proof</a> ·
57
+ <a href="#privacy">Privacy</a> ·
58
+ <a href="#the-paper">Paper</a> ·
59
+ <a href="docs/">Docs</a>
60
+ </p>
61
+
62
+ ---
63
+
64
+ > Switchboard routes prompts to the right model while preserving context through
65
+ > local semantic memory and context compression, keeping sensitive work local,
66
+ > and reducing unnecessary premium-model usage.
67
+
68
+ It's built for the single-workstation setup where the scarce resources aren't
69
+ dollars-per-token but **subscription quota**, **privacy**, and a pile of
70
+ **heterogeneous agent interfaces**.
71
+
72
+ ## What it does
73
+
74
+ - **Routes** across local [Ollama](https://ollama.com) models, the **Codex** CLI, and **Claude Code** — deterministic rules first, with optional tiny learned classifiers for recall.
75
+ - **Private mode** — a deterministic keyword/PII/secret-format floor blocks sensitive prompts from ever reaching a subscription backend, even on fallback.
76
+ - **Grounds** answers with deterministic tools (time/date, safe calculator, unit conversion, keyless live stock & news) instead of letting a model guess.
77
+ - **Carries context** across backend switches: recent user, assistant, and tool turns are assembled into one redacted session prompt.
78
+ - **Compresses** long context with a Headroom-inspired layer; the model-boundary pass only summarizes recent conversation, while trusted facts, retrieved memory, and the current request survive intact.
79
+ - **Remembers** across backends via local embedding-based semantic memory, with SQLite search available for direct memory lookup.
80
+ - **Explains every decision** and records metadata-only telemetry (no prompt/response bodies).
81
+ - **Ships its own evaluation** — a 100-case quality benchmark, a local LLM-as-judge, and a multi-run statistical harness.
82
+
83
+ ## How it works
84
+
85
+ ```
86
+ UI / CLI ──► Session manager (shared history across all backends)
87
+
88
+
89
+ Capability detector (regex) ◄──► deterministic tools
90
+ │ (learned tool dispatcher recovers misses; tool verifies)
91
+
92
+ Privacy floor (keywords + PII + secret formats — a match is FINAL)
93
+ │ (learned sensitivity escalator may only ADD protection)
94
+
95
+ Deterministic policy ← always wins; unknown ⇒ local
96
+ │ (learned router supplies recall: tool / local / coding / reasoning)
97
+
98
+ Context builder + redaction ◄── semantic memory
99
+
100
+
101
+ Compression (metadata + history-only context pass)
102
+
103
+
104
+ Ollama (default) │ Codex (coding) │ Claude Code (reasoning)
105
+
106
+
107
+ Response sanitizer ─► metadata-only telemetry
108
+ ```
109
+
110
+ The organizing invariant: **deterministic policy always precedes and overrides
111
+ the learned components.** Privacy, tool grounding, forced selection, and
112
+ fallback keep working even when the local model runtime — and therefore every
113
+ learned component — is down.
114
+
115
+ ## Get started (60 seconds)
116
+
117
+ PyPI release is pending. Until the first release is published:
118
+
119
+ ```bash
120
+ pip install "git+https://github.com/aivinay/switchboard.git"
121
+ ```
122
+
123
+ After the PyPI release:
124
+
125
+ ```bash
126
+ pip install switchboard-local
127
+ ```
128
+
129
+ ```bash
130
+ # point it at a local model runtime (install Ollama, then pull a small model)
131
+ ollama pull llama3.2:3b
132
+
133
+ # sanity-check your setup
134
+ switchboard doctor
135
+
136
+ # ask — Switchboard routes it, grounds it, and tells you why
137
+ switchboard ask "summarize this error log and suggest a fix"
138
+
139
+ # see the routing decision without running anything
140
+ switchboard route "refactor the auth module and add tests"
141
+
142
+ # prefer your browser? launch the local web UI, then open http://127.0.0.1:8080/ui
143
+ switchboard ui
144
+ ```
145
+
146
+ Requires **Python 3.11+**. Codex / Claude Code backends are optional — without
147
+ them, everything routes locally. See [docs/usage.md](docs/usage.md).
148
+
149
+ ## Context, memory, and tokens
150
+
151
+ Switchboard has two user-facing CLI surfaces:
152
+
153
+ - `switchboard route ...` and bare `switchboard ask ...` use the personal local-first route/call workflow.
154
+ - The web UI and `switchboard ask --backend auto ...` use the stateful core workflow: shared sessions, model switching, semantic-memory retrieval, context-boundary compression, and backend telemetry all run on the same path.
155
+
156
+ Example stateful CLI session:
157
+
158
+ ```bash
159
+ switchboard ask --backend auto --new-session "Remember: prefer local models for private notes."
160
+ switchboard ask --backend auto --session <session_id> --memory "What should you remember?"
161
+ ```
162
+
163
+ Long prompts and long sessions record token estimates and savings metadata. The request-level pass can shorten an oversized raw prompt; the context-boundary pass then compresses only `<recent_conversation>`. The `<trusted_facts>`, `<long_term_memory>`, and `<current_user_request>` blocks are protected from that second pass so grounding and intent are not traded away for token budget.
164
+
165
+ Memory is local. `switchboard memory add` stores the item in SQLite and, when `semantic_memory_enabled` is on and Ollama can serve `nomic-embed-text`, indexes an embedding for cross-backend retrieval. `switchboard memory search` works as local text search even when embeddings are unavailable.
166
+
167
+ Details: [docs/context-memory-compression.md](docs/context-memory-compression.md).
168
+
169
+ ## Proof
170
+
171
+ A 100-case benchmark across five task categories (coding, reasoning,
172
+ summarization, private, grounding), run on real backends and judged by a local
173
+ model, over **multiple independent runs** (means shown; full per-condition
174
+ numbers, confidence intervals, and significance tests are in the paper):
175
+
176
+ | Policy | Quality (1–5) | Premium usage | Privacy leaks | Answered |
177
+ |-------------------|:-------------:|:-------------:|:-------------:|:--------:|
178
+ | always-local | 3.4 | 0% | **0** | 100% |
179
+ | rules | 3.8 | 27% | **0** | 100% |
180
+ | hybrid | 3.9 | 28% | **0** | 100% |
181
+ | **learned** | **4.1** | 38% | **0** | 100% |
182
+ | always-premium | 4.6 | 100% | **0** | 61%¹ |
183
+
184
+ <sub>¹ The "just use the premium agent for everything" baseline must <em>block</em> every
185
+ sensitive prompt to stay leak-free, so its coverage collapses — exactly the gap
186
+ Switchboard closes. <strong>Zero measured leaks in every condition and every run.</strong></sub>
187
+
188
+ These numbers come from a real-backend benchmark whose full harness travels with the paper's [reproduction bundle on Zenodo](https://doi.org/10.5281/zenodo.20789935).
189
+
190
+ ## Privacy
191
+
192
+ Switchboard is local-first and privacy-aware by construction:
193
+
194
+ - The **deterministic privacy floor runs before any non-local routing**; a positive verdict is final and cannot be overridden by a learned component or by prompt wording.
195
+ - **Secret-format detection** (cloud keys, JWTs, PEM blocks, env credentials) shares its patterns with context redaction, so the routing boundary and the redactor can't drift apart.
196
+ - **Metadata-only telemetry** — prompt and response bodies are not stored by default.
197
+ - Semantic-memory **embeddings and the eval judge run locally**.
198
+
199
+ Switchboard deliberately does **not** resell API access, scrape web UIs, or
200
+ bypass provider limits — subscription CLIs are invoked exactly as the
201
+ authenticated user could invoke them, in read-only sandbox modes. See
202
+ [SECURITY.md](SECURITY.md) and [docs/privacy.md](docs/privacy.md).
203
+
204
+ <details>
205
+ <summary><b>What's inside</b></summary>
206
+
207
+ - **Deterministic router** — keyword rules; unknown prompts default local-first.
208
+ - **Learned router / tool dispatcher / sensitivity escalator** — tiny softmax classifiers over a locally-computed embedding (~50 ms, pure-Python inference), each retrainable in seconds from your own thumbs-down corrections behind golden-accuracy gates. They fail closed to the deterministic path.
209
+ - **Tools** — time/date with timezones, safe abstract-syntax-tree calculator, unit conversion, keyless live stock quotes & news.
210
+ - **Compression** — structure-aware, deterministic, dependency-free; preserves task header, code blocks, tracebacks, and grounded facts.
211
+ - **Semantic memory** — `nomic-embed-text` embeddings, cosine retrieval, local memory commands, and SQLite text-search fallback for direct search.
212
+ - **Evaluation** — mock evals (CI), real-backend smoke suite, 100-case quality benchmark, adversarial tester/developer dogfooding loop.
213
+
214
+ </details>
215
+
216
+ ## Configuration
217
+
218
+ Settings live in `config/personal.yaml` (ships with safe local-first defaults —
219
+ see `config/personal.example.yaml`). Highlights:
220
+
221
+ ```yaml
222
+ preferences:
223
+ router_mode: "learned" # rules | llm | hybrid | learned
224
+ private_mode: true # block sensitive prompts from non-local backends
225
+ allow_cloud: false
226
+ compression_enabled: true
227
+ compression_threshold_tokens: 1000
228
+ semantic_memory_enabled: true
229
+ semantic_memory_top_k: 3
230
+ finance_provider: "yahoo"
231
+ news_provider: "google_news_rss"
232
+ ```
233
+
234
+ Provider API keys are referenced **by environment-variable name** (e.g.
235
+ `OPENAI_API_KEY`), never inline. See [docs/overrides.md](docs/overrides.md).
236
+
237
+ ## The paper
238
+
239
+ Switchboard is described in a preprint — *"Privacy-Aware Hybrid Routing Across
240
+ Heterogeneous AI Agents on a Single Workstation."* The manuscript, the multi-run
241
+ benchmark harness, the statistical-aggregation and figure scripts, and the
242
+ per-case records are archived together as a reproduction bundle on Zenodo:
243
+ [10.5281/zenodo.20789935](https://doi.org/10.5281/zenodo.20789935).
244
+
245
+ This repository ships only the software. It deliberately does not carry the
246
+ paper's experiment-running or figure-generation tooling — that lives with the
247
+ archival record so the code stays focused on the router itself.
248
+
249
+ ## Development
250
+
251
+ ```bash
252
+ make install # .venv + editable install with dev extras
253
+ make check # ruff + mypy + the full test suite
254
+ ```
255
+
256
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Issues and PRs welcome — please preserve
257
+ the privacy invariant described there.
258
+
259
+ ## Citing Switchboard
260
+
261
+ A preprint is available on Zenodo with a citable DOI —
262
+ [10.5281/zenodo.20789935](https://doi.org/10.5281/zenodo.20789935). See
263
+ [CITATION.cff](CITATION.cff) for machine-readable metadata.
264
+
265
+ > V. Gupta, "Switchboard: Privacy-Aware Hybrid Routing Across Heterogeneous AI
266
+ > Agents on a Single Workstation," Zenodo, 2026, doi:10.5281/zenodo.20789935.
267
+
268
+ ## License
269
+
270
+ [MIT](LICENSE) © 2026 Vinay Gupta
@@ -0,0 +1,231 @@
1
+ <h1 align="center">Switchboard</h1>
2
+
3
+ <p align="center"><strong>A privacy-aware, local-first router across your CLI coding agents and local LLMs.</strong></p>
4
+
5
+ <p align="center">
6
+ <a href="https://github.com/aivinay/switchboard/actions/workflows/ci.yml"><img src="https://github.com/aivinay/switchboard/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
7
+ <a href="RELEASE.md"><img src="https://img.shields.io/badge/PyPI-pending-lightgrey.svg" alt="PyPI: pending"></a>
8
+ <img src="https://img.shields.io/badge/python-3.11%2B-blue.svg" alt="Python 3.11+">
9
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License: MIT"></a>
10
+ <a href="https://doi.org/10.5281/zenodo.20789935"><img src="https://img.shields.io/badge/DOI-10.5281%2Fzenodo.20789935-blue.svg" alt="DOI"></a>
11
+ </p>
12
+
13
+ <p align="center">
14
+ <a href="#get-started-60-seconds">Install</a> ·
15
+ <a href="#how-it-works">How it works</a> ·
16
+ <a href="#context-memory-and-tokens">Context</a> ·
17
+ <a href="#proof">Proof</a> ·
18
+ <a href="#privacy">Privacy</a> ·
19
+ <a href="#the-paper">Paper</a> ·
20
+ <a href="docs/">Docs</a>
21
+ </p>
22
+
23
+ ---
24
+
25
+ > Switchboard routes prompts to the right model while preserving context through
26
+ > local semantic memory and context compression, keeping sensitive work local,
27
+ > and reducing unnecessary premium-model usage.
28
+
29
+ It's built for the single-workstation setup where the scarce resources aren't
30
+ dollars-per-token but **subscription quota**, **privacy**, and a pile of
31
+ **heterogeneous agent interfaces**.
32
+
33
+ ## What it does
34
+
35
+ - **Routes** across local [Ollama](https://ollama.com) models, the **Codex** CLI, and **Claude Code** — deterministic rules first, with optional tiny learned classifiers for recall.
36
+ - **Private mode** — a deterministic keyword/PII/secret-format floor blocks sensitive prompts from ever reaching a subscription backend, even on fallback.
37
+ - **Grounds** answers with deterministic tools (time/date, safe calculator, unit conversion, keyless live stock & news) instead of letting a model guess.
38
+ - **Carries context** across backend switches: recent user, assistant, and tool turns are assembled into one redacted session prompt.
39
+ - **Compresses** long context with a Headroom-inspired layer; the model-boundary pass only summarizes recent conversation, while trusted facts, retrieved memory, and the current request survive intact.
40
+ - **Remembers** across backends via local embedding-based semantic memory, with SQLite search available for direct memory lookup.
41
+ - **Explains every decision** and records metadata-only telemetry (no prompt/response bodies).
42
+ - **Ships its own evaluation** — a 100-case quality benchmark, a local LLM-as-judge, and a multi-run statistical harness.
43
+
44
+ ## How it works
45
+
46
+ ```
47
+ UI / CLI ──► Session manager (shared history across all backends)
48
+
49
+
50
+ Capability detector (regex) ◄──► deterministic tools
51
+ │ (learned tool dispatcher recovers misses; tool verifies)
52
+
53
+ Privacy floor (keywords + PII + secret formats — a match is FINAL)
54
+ │ (learned sensitivity escalator may only ADD protection)
55
+
56
+ Deterministic policy ← always wins; unknown ⇒ local
57
+ │ (learned router supplies recall: tool / local / coding / reasoning)
58
+
59
+ Context builder + redaction ◄── semantic memory
60
+
61
+
62
+ Compression (metadata + history-only context pass)
63
+
64
+
65
+ Ollama (default) │ Codex (coding) │ Claude Code (reasoning)
66
+
67
+
68
+ Response sanitizer ─► metadata-only telemetry
69
+ ```
70
+
71
+ The organizing invariant: **deterministic policy always precedes and overrides
72
+ the learned components.** Privacy, tool grounding, forced selection, and
73
+ fallback keep working even when the local model runtime — and therefore every
74
+ learned component — is down.
75
+
76
+ ## Get started (60 seconds)
77
+
78
+ PyPI release is pending. Until the first release is published:
79
+
80
+ ```bash
81
+ pip install "git+https://github.com/aivinay/switchboard.git"
82
+ ```
83
+
84
+ After the PyPI release:
85
+
86
+ ```bash
87
+ pip install switchboard-local
88
+ ```
89
+
90
+ ```bash
91
+ # point it at a local model runtime (install Ollama, then pull a small model)
92
+ ollama pull llama3.2:3b
93
+
94
+ # sanity-check your setup
95
+ switchboard doctor
96
+
97
+ # ask — Switchboard routes it, grounds it, and tells you why
98
+ switchboard ask "summarize this error log and suggest a fix"
99
+
100
+ # see the routing decision without running anything
101
+ switchboard route "refactor the auth module and add tests"
102
+
103
+ # prefer your browser? launch the local web UI, then open http://127.0.0.1:8080/ui
104
+ switchboard ui
105
+ ```
106
+
107
+ Requires **Python 3.11+**. Codex / Claude Code backends are optional — without
108
+ them, everything routes locally. See [docs/usage.md](docs/usage.md).
109
+
110
+ ## Context, memory, and tokens
111
+
112
+ Switchboard has two user-facing CLI surfaces:
113
+
114
+ - `switchboard route ...` and bare `switchboard ask ...` use the personal local-first route/call workflow.
115
+ - The web UI and `switchboard ask --backend auto ...` use the stateful core workflow: shared sessions, model switching, semantic-memory retrieval, context-boundary compression, and backend telemetry all run on the same path.
116
+
117
+ Example stateful CLI session:
118
+
119
+ ```bash
120
+ switchboard ask --backend auto --new-session "Remember: prefer local models for private notes."
121
+ switchboard ask --backend auto --session <session_id> --memory "What should you remember?"
122
+ ```
123
+
124
+ Long prompts and long sessions record token estimates and savings metadata. The request-level pass can shorten an oversized raw prompt; the context-boundary pass then compresses only `<recent_conversation>`. The `<trusted_facts>`, `<long_term_memory>`, and `<current_user_request>` blocks are protected from that second pass so grounding and intent are not traded away for token budget.
125
+
126
+ Memory is local. `switchboard memory add` stores the item in SQLite and, when `semantic_memory_enabled` is on and Ollama can serve `nomic-embed-text`, indexes an embedding for cross-backend retrieval. `switchboard memory search` works as local text search even when embeddings are unavailable.
127
+
128
+ Details: [docs/context-memory-compression.md](docs/context-memory-compression.md).
129
+
130
+ ## Proof
131
+
132
+ A 100-case benchmark across five task categories (coding, reasoning,
133
+ summarization, private, grounding), run on real backends and judged by a local
134
+ model, over **multiple independent runs** (means shown; full per-condition
135
+ numbers, confidence intervals, and significance tests are in the paper):
136
+
137
+ | Policy | Quality (1–5) | Premium usage | Privacy leaks | Answered |
138
+ |-------------------|:-------------:|:-------------:|:-------------:|:--------:|
139
+ | always-local | 3.4 | 0% | **0** | 100% |
140
+ | rules | 3.8 | 27% | **0** | 100% |
141
+ | hybrid | 3.9 | 28% | **0** | 100% |
142
+ | **learned** | **4.1** | 38% | **0** | 100% |
143
+ | always-premium | 4.6 | 100% | **0** | 61%¹ |
144
+
145
+ <sub>¹ The "just use the premium agent for everything" baseline must <em>block</em> every
146
+ sensitive prompt to stay leak-free, so its coverage collapses — exactly the gap
147
+ Switchboard closes. <strong>Zero measured leaks in every condition and every run.</strong></sub>
148
+
149
+ These numbers come from a real-backend benchmark whose full harness travels with the paper's [reproduction bundle on Zenodo](https://doi.org/10.5281/zenodo.20789935).
150
+
151
+ ## Privacy
152
+
153
+ Switchboard is local-first and privacy-aware by construction:
154
+
155
+ - The **deterministic privacy floor runs before any non-local routing**; a positive verdict is final and cannot be overridden by a learned component or by prompt wording.
156
+ - **Secret-format detection** (cloud keys, JWTs, PEM blocks, env credentials) shares its patterns with context redaction, so the routing boundary and the redactor can't drift apart.
157
+ - **Metadata-only telemetry** — prompt and response bodies are not stored by default.
158
+ - Semantic-memory **embeddings and the eval judge run locally**.
159
+
160
+ Switchboard deliberately does **not** resell API access, scrape web UIs, or
161
+ bypass provider limits — subscription CLIs are invoked exactly as the
162
+ authenticated user could invoke them, in read-only sandbox modes. See
163
+ [SECURITY.md](SECURITY.md) and [docs/privacy.md](docs/privacy.md).
164
+
165
+ <details>
166
+ <summary><b>What's inside</b></summary>
167
+
168
+ - **Deterministic router** — keyword rules; unknown prompts default local-first.
169
+ - **Learned router / tool dispatcher / sensitivity escalator** — tiny softmax classifiers over a locally-computed embedding (~50 ms, pure-Python inference), each retrainable in seconds from your own thumbs-down corrections behind golden-accuracy gates. They fail closed to the deterministic path.
170
+ - **Tools** — time/date with timezones, safe abstract-syntax-tree calculator, unit conversion, keyless live stock quotes & news.
171
+ - **Compression** — structure-aware, deterministic, dependency-free; preserves task header, code blocks, tracebacks, and grounded facts.
172
+ - **Semantic memory** — `nomic-embed-text` embeddings, cosine retrieval, local memory commands, and SQLite text-search fallback for direct search.
173
+ - **Evaluation** — mock evals (CI), real-backend smoke suite, 100-case quality benchmark, adversarial tester/developer dogfooding loop.
174
+
175
+ </details>
176
+
177
+ ## Configuration
178
+
179
+ Settings live in `config/personal.yaml` (ships with safe local-first defaults —
180
+ see `config/personal.example.yaml`). Highlights:
181
+
182
+ ```yaml
183
+ preferences:
184
+ router_mode: "learned" # rules | llm | hybrid | learned
185
+ private_mode: true # block sensitive prompts from non-local backends
186
+ allow_cloud: false
187
+ compression_enabled: true
188
+ compression_threshold_tokens: 1000
189
+ semantic_memory_enabled: true
190
+ semantic_memory_top_k: 3
191
+ finance_provider: "yahoo"
192
+ news_provider: "google_news_rss"
193
+ ```
194
+
195
+ Provider API keys are referenced **by environment-variable name** (e.g.
196
+ `OPENAI_API_KEY`), never inline. See [docs/overrides.md](docs/overrides.md).
197
+
198
+ ## The paper
199
+
200
+ Switchboard is described in a preprint — *"Privacy-Aware Hybrid Routing Across
201
+ Heterogeneous AI Agents on a Single Workstation."* The manuscript, the multi-run
202
+ benchmark harness, the statistical-aggregation and figure scripts, and the
203
+ per-case records are archived together as a reproduction bundle on Zenodo:
204
+ [10.5281/zenodo.20789935](https://doi.org/10.5281/zenodo.20789935).
205
+
206
+ This repository ships only the software. It deliberately does not carry the
207
+ paper's experiment-running or figure-generation tooling — that lives with the
208
+ archival record so the code stays focused on the router itself.
209
+
210
+ ## Development
211
+
212
+ ```bash
213
+ make install # .venv + editable install with dev extras
214
+ make check # ruff + mypy + the full test suite
215
+ ```
216
+
217
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Issues and PRs welcome — please preserve
218
+ the privacy invariant described there.
219
+
220
+ ## Citing Switchboard
221
+
222
+ A preprint is available on Zenodo with a citable DOI —
223
+ [10.5281/zenodo.20789935](https://doi.org/10.5281/zenodo.20789935). See
224
+ [CITATION.cff](CITATION.cff) for machine-readable metadata.
225
+
226
+ > V. Gupta, "Switchboard: Privacy-Aware Hybrid Routing Across Heterogeneous AI
227
+ > Agents on a Single Workstation," Zenodo, 2026, doi:10.5281/zenodo.20789935.
228
+
229
+ ## License
230
+
231
+ [MIT](LICENSE) © 2026 Vinay Gupta
@@ -0,0 +1,85 @@
1
+ [project]
2
+ name = "switchboard-local"
3
+ version = "0.1.0"
4
+ description = "Privacy-aware, local-first router across CLI coding agents (Codex, Claude Code) and local LLMs (Ollama)."
5
+ readme = "README.md"
6
+ requires-python = ">=3.11"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ authors = [{ name = "Vinay Gupta", email = "ai.vinaygupta@gmail.com" }]
10
+ keywords = [
11
+ "llm", "llm-routing", "local-first", "privacy", "ollama",
12
+ "claude-code", "codex", "ai-agents", "orchestration", "on-device",
13
+ ]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Intended Audience :: Developers",
17
+ "Intended Audience :: Science/Research",
18
+ "Operating System :: OS Independent",
19
+ "Programming Language :: Python :: 3",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
23
+ "Topic :: Software Development :: Libraries :: Python Modules",
24
+ ]
25
+ dependencies = [
26
+ "fastapi>=0.115.0",
27
+ "httpx>=0.27.0",
28
+ "pydantic>=2.8.0",
29
+ "pydantic-settings>=2.4.0",
30
+ "pyyaml>=6.0.2",
31
+ "sqlmodel>=0.0.22",
32
+ "uvicorn[standard]>=0.30.0",
33
+ ]
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/aivinay/switchboard"
37
+ Repository = "https://github.com/aivinay/switchboard"
38
+ Issues = "https://github.com/aivinay/switchboard/issues"
39
+
40
+ [project.scripts]
41
+ switchboard = "switchboard.cli:main"
42
+ ai-switchboard = "switchboard.cli:main"
43
+
44
+ [build-system]
45
+ requires = ["setuptools>=69", "wheel"]
46
+ build-backend = "setuptools.build_meta"
47
+
48
+ [tool.setuptools.packages.find]
49
+ include = ["switchboard*"]
50
+
51
+ [tool.setuptools.package-data]
52
+ "switchboard.config" = ["*.yaml", "*.json"]
53
+
54
+ [project.optional-dependencies]
55
+ dev = [
56
+ "mypy>=1.11.0",
57
+ "pytest>=8.3.0",
58
+ "ruff>=0.6.0",
59
+ ]
60
+ finance = [
61
+ "yfinance>=0.2.50",
62
+ ]
63
+ router = [
64
+ "numpy>=1.26",
65
+ ]
66
+
67
+ [tool.pytest.ini_options]
68
+ testpaths = ["tests"]
69
+ pythonpath = ["."]
70
+
71
+ [tool.ruff]
72
+ line-length = 100
73
+ target-version = "py311"
74
+
75
+ [tool.ruff.lint]
76
+ select = ["E", "F", "I", "UP", "B", "SIM"]
77
+
78
+ [tool.mypy]
79
+ python_version = "3.11"
80
+ plugins = ["pydantic.mypy"]
81
+ warn_unused_ignores = true
82
+ warn_redundant_casts = true
83
+ check_untyped_defs = true
84
+ disallow_untyped_defs = false
85
+ ignore_missing_imports = true
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,8 @@
1
+ """Switchboard package."""
2
+
3
+ from importlib.metadata import PackageNotFoundError, version
4
+
5
+ try:
6
+ __version__ = version("switchboard-local")
7
+ except PackageNotFoundError:
8
+ __version__ = "0.1.0"
@@ -0,0 +1 @@
1
+ """FastAPI application package."""
@@ -0,0 +1 @@
1
+ """API routers."""