uhb-harness 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 (89) hide show
  1. uhb_harness-0.1.0/PKG-INFO +341 -0
  2. uhb_harness-0.1.0/README.md +323 -0
  3. uhb_harness-0.1.0/pyproject.toml +66 -0
  4. uhb_harness-0.1.0/setup.cfg +4 -0
  5. uhb_harness-0.1.0/uhb/LICENSE +18 -0
  6. uhb_harness-0.1.0/uhb/LICENSE-IMPLEMENTATION.md +112 -0
  7. uhb_harness-0.1.0/uhb/README.md +318 -0
  8. uhb_harness-0.1.0/uhb/__init__.py +34 -0
  9. uhb_harness-0.1.0/uhb/adapters/__init__.py +6 -0
  10. uhb_harness-0.1.0/uhb/adapters/base.py +81 -0
  11. uhb_harness-0.1.0/uhb/adapters/harness_maxxing.py +68 -0
  12. uhb_harness-0.1.0/uhb/adapters/hermes.py +154 -0
  13. uhb_harness-0.1.0/uhb/adapters/openclaw.py +61 -0
  14. uhb_harness-0.1.0/uhb/adapters/second_brain.py +296 -0
  15. uhb_harness-0.1.0/uhb/config.example.yaml +45 -0
  16. uhb_harness-0.1.0/uhb/core/__init__.py +36 -0
  17. uhb_harness-0.1.0/uhb/core/contract.py +73 -0
  18. uhb_harness-0.1.0/uhb/core/executor.py +115 -0
  19. uhb_harness-0.1.0/uhb/core/health.py +86 -0
  20. uhb_harness-0.1.0/uhb/core/memory.py +109 -0
  21. uhb_harness-0.1.0/uhb/core/router.py +155 -0
  22. uhb_harness-0.1.0/uhb/core/skills.py +104 -0
  23. uhb_harness-0.1.0/uhb/core/types.py +120 -0
  24. uhb_harness-0.1.0/uhb/docs/MANUEL_UHB.md +316 -0
  25. uhb_harness-0.1.0/uhb/docs/MANUEL_UHB.pdf +0 -0
  26. uhb_harness-0.1.0/uhb/docs/MANUEL_UHB_EN.md +320 -0
  27. uhb_harness-0.1.0/uhb/docs/MANUEL_UHB_EN.pdf +0 -0
  28. uhb_harness-0.1.0/uhb/docs/UI_INTEGRATION.md +274 -0
  29. uhb_harness-0.1.0/uhb/entrypoints/__init__.py +9 -0
  30. uhb_harness-0.1.0/uhb/entrypoints/cli.py +496 -0
  31. uhb_harness-0.1.0/uhb/entrypoints/fake_claude.py +185 -0
  32. uhb_harness-0.1.0/uhb/entrypoints/panel.py +326 -0
  33. uhb_harness-0.1.0/uhb/entrypoints/server.py +301 -0
  34. uhb_harness-0.1.0/uhb/entrypoints/settings.py +143 -0
  35. uhb_harness-0.1.0/uhb/entrypoints/status.py +135 -0
  36. uhb_harness-0.1.0/uhb/entrypoints/update.py +136 -0
  37. uhb_harness-0.1.0/uhb/hub/__init__.py +27 -0
  38. uhb_harness-0.1.0/uhb/hub/guard.py +110 -0
  39. uhb_harness-0.1.0/uhb/hub/modes/__init__.py +357 -0
  40. uhb_harness-0.1.0/uhb/hub/modes/profiles/chat.json +18 -0
  41. uhb_harness-0.1.0/uhb/hub/modes/profiles/dev.json +19 -0
  42. uhb_harness-0.1.0/uhb/hub/modes/profiles/observe.json +18 -0
  43. uhb_harness-0.1.0/uhb/hub/modes/profiles/review.json +19 -0
  44. uhb_harness-0.1.0/uhb/hub/router/__init__.py +568 -0
  45. uhb_harness-0.1.0/uhb/hub/router/catalog.json +31 -0
  46. uhb_harness-0.1.0/uhb/hub/wiki/__init__.py +27 -0
  47. uhb_harness-0.1.0/uhb/hub/wiki/scripts/__init__.py +469 -0
  48. uhb_harness-0.1.0/uhb/hub/wiki/scripts/ingest.py +490 -0
  49. uhb_harness-0.1.0/uhb/hub/wiki/scripts/lint.py +297 -0
  50. uhb_harness-0.1.0/uhb/hub/wiki/scripts/query.py +209 -0
  51. uhb_harness-0.1.0/uhb/hub/wiki/wiki/contradictions.md +8 -0
  52. uhb_harness-0.1.0/uhb/hub/wiki/wiki/index.md +19 -0
  53. uhb_harness-0.1.0/uhb/hub/wiki/wiki/log.md +5 -0
  54. uhb_harness-0.1.0/uhb/hub/wiki/wiki/schema.md +71 -0
  55. uhb_harness-0.1.0/uhb/services/__init__.py +15 -0
  56. uhb_harness-0.1.0/uhb/services/install.py +284 -0
  57. uhb_harness-0.1.0/uhb/services/mari/README.md +33 -0
  58. uhb_harness-0.1.0/uhb/services/mari/bridge/Bridge.csproj +20 -0
  59. uhb_harness-0.1.0/uhb/services/mari/bridge/IJournalWriterVGrain.cs +15 -0
  60. uhb_harness-0.1.0/uhb/services/mari/bridge/IRemediationWriterVGrain.cs +15 -0
  61. uhb_harness-0.1.0/uhb/services/mari/bridge/JournalWriterVGrain.cs +26 -0
  62. uhb_harness-0.1.0/uhb/services/mari/bridge/Program.cs +152 -0
  63. uhb_harness-0.1.0/uhb/services/mari/bridge/RemediationWriterVGrain.cs +28 -0
  64. uhb_harness-0.1.0/uhb/services/mari/bridge/appsettings.Development.json +8 -0
  65. uhb_harness-0.1.0/uhb/services/mari/bridge/appsettings.json +17 -0
  66. uhb_harness-0.1.0/uhb/services/rag/README.md +57 -0
  67. uhb_harness-0.1.0/uhb/services/rag/chercher.py +335 -0
  68. uhb_harness-0.1.0/uhb/services/rag/requirements.txt +14 -0
  69. uhb_harness-0.1.0/uhb/services/rag/serveur_rag.py +190 -0
  70. uhb_harness-0.1.0/uhb/tests/__init__.py +5 -0
  71. uhb_harness-0.1.0/uhb/tests/test_cli.py +453 -0
  72. uhb_harness-0.1.0/uhb/tests/test_contract.py +98 -0
  73. uhb_harness-0.1.0/uhb/tests/test_e2e_harness.py +259 -0
  74. uhb_harness-0.1.0/uhb/tests/test_entrypoints.py +147 -0
  75. uhb_harness-0.1.0/uhb/tests/test_hub_gate.py +292 -0
  76. uhb_harness-0.1.0/uhb/tests/test_hub_modes.py +246 -0
  77. uhb_harness-0.1.0/uhb/tests/test_hub_router.py +414 -0
  78. uhb_harness-0.1.0/uhb/tests/test_hub_wiki.py +486 -0
  79. uhb_harness-0.1.0/uhb/tests/test_hub_wiring.py +302 -0
  80. uhb_harness-0.1.0/uhb/tests/test_memory.py +120 -0
  81. uhb_harness-0.1.0/uhb/tests/test_router.py +88 -0
  82. uhb_harness-0.1.0/uhb/tests/test_second_brain.py +315 -0
  83. uhb_harness-0.1.0/uhb/tests/test_services.py +270 -0
  84. uhb_harness-0.1.0/uhb_harness.egg-info/PKG-INFO +341 -0
  85. uhb_harness-0.1.0/uhb_harness.egg-info/SOURCES.txt +87 -0
  86. uhb_harness-0.1.0/uhb_harness.egg-info/dependency_links.txt +1 -0
  87. uhb_harness-0.1.0/uhb_harness.egg-info/entry_points.txt +2 -0
  88. uhb_harness-0.1.0/uhb_harness.egg-info/requires.txt +8 -0
  89. uhb_harness-0.1.0/uhb_harness.egg-info/top_level.txt +1 -0
@@ -0,0 +1,341 @@
1
+ Metadata-Version: 2.4
2
+ Name: uhb-harness
3
+ Version: 0.1.0
4
+ Summary: Universal Harness Backend: execution modes, intent routing and a compiled wiki for any agent harness.
5
+ Author: Thomas Leroyer
6
+ License-Expression: FSL-1.1-MIT
7
+ Project-URL: Homepage, https://github.com/Antoine-Thomas/UHB-Universal-Harness-Backend
8
+ Keywords: agents,harness,router,llm,control-plane,wiki
9
+ Classifier: Programming Language :: Python :: 3.11
10
+ Classifier: Programming Language :: Python :: 3.12
11
+ Classifier: Operating System :: OS Independent
12
+ Requires-Python: >=3.11
13
+ Description-Content-Type: text/markdown
14
+ License-File: uhb/LICENSE
15
+ License-File: uhb/LICENSE-IMPLEMENTATION.md
16
+ Requires-Dist: httpx>=0.25
17
+ Provides-Extra: yaml
18
+ Requires-Dist: pyyaml>=6; extra == "yaml"
19
+ Provides-Extra: test
20
+ Requires-Dist: pytest>=8; extra == "test"
21
+ Requires-Dist: pyyaml>=6; extra == "test"
22
+ Dynamic: license-file
23
+
24
+ # UHB — Universal Harness Backend
25
+
26
+ > **Your harness gives you the body — the UI, the sessions, the automations.
27
+ > UHB gives it the brain — the memory, the routing, the absolute permissions.**
28
+
29
+ UHB is not another agent. It is the execution backend underneath one: modes that
30
+ decide what a turn may do, a router that decides which model answers, a write gate
31
+ that refuses before it touches a file, and a compiled wiki that remembers.
32
+
33
+ Harness Maxxing, OpenClaw, Hermes Agent or your own CLI plug into it — and stop
34
+ caring which vendor's model their harness was written against.
35
+
36
+ Status: **0.1.0** — wiki, modes, router and write gate are in service; this
37
+ release adds the CLI, the panel, the embedded manual and the packaging. Every
38
+ number below was measured on the reference machine and is reproducible with the
39
+ command next to it.
40
+
41
+ Distribution name: `uhb-harness` on PyPI (`uhb` is taken by an unrelated
42
+ project); the command is `uhb`.
43
+
44
+ ---
45
+
46
+ ## Why this exists
47
+
48
+ ### 1. Routing that costs nothing
49
+
50
+ The intent router classifies a request and plans a model chain in **0.013 ms**
51
+ (p50) — no model call, no tokens. It is deterministic: same request, same policy,
52
+ same chain, every time. You can inspect the decision before spending anything.
53
+
54
+ | Measurement | Value | How |
55
+ | --- | --- | --- |
56
+ | `route()` | p50 0.013 ms · p95 0.020 ms · p99 0.026 ms | 2000 calls, `perf_counter` |
57
+ | `classify()` | p50 0.007 ms | 2000 calls |
58
+ | routing policy load | 0.50 ms cold · 0.22 ms cached | 5 models, 3 tiers |
59
+
60
+ ### 2. Fail-closed permissions, not suggestions
61
+
62
+ Four execution modes, each a JSON file: `chat`, `dev`, `observe`, `review`. A
63
+ mode's write scope is a list of globs; **`deny` wins over everything**, an unknown
64
+ key in a profile is a load error, and a write path with no mode raises
65
+ `ModeRequired` instead of assuming one.
66
+
67
+ ```bash
68
+ uhb modes show review # write: [] — the agent physically cannot edit your files
69
+ ```
70
+
71
+ A refused compile leaves every byte of the tree exactly as it was: all planned
72
+ writes are checked first, then written. Measured in the test suite by comparing a
73
+ byte-level snapshot of the whole tree before and after a refused write.
74
+
75
+ ### 3. No vendor lock-in
76
+
77
+ | What | Where it lives |
78
+ | --- | --- |
79
+ | How to reach a provider | `uhb/config.yaml` (protocol, base URL, key **variable name**) |
80
+ | When to pick a model | `router/catalog.json` (tier, supported complexity) |
81
+ | What a turn may do | `hub/modes/profiles/*.json` |
82
+
83
+ Adding a vendor means one entry in the provider table. Adding a model to the policy
84
+ means one line in the catalog. A catalog entry no provider can serve is refused at
85
+ load time — a route pointing nowhere is worse than no route.
86
+
87
+ ---
88
+
89
+ ## The CLI
90
+
91
+ ```bash
92
+ uhb status # version, git state, providers, policy, modes
93
+ uhb status --json # same, machine-readable
94
+ uhb modes list # name, kind, tier, write scope, turn budget
95
+ uhb modes show dev # the whole profile
96
+ uhb config list # every effective key (defaults included)
97
+ uhb config get routing.default
98
+ uhb config set routing.default deepseek/deepseek-flash
99
+ uhb run --mode dev --port 8080 # HTTP entrypoint (OpenAI + Anthropic shapes)
100
+ uhb execute --mode dev "..." # one turn through the configured harness
101
+ uhb update --dry-run # fetch and report; nothing moves
102
+ uhb update # git pull --ff-only, then prove it
103
+ uhb docs # path and URL of the embedded manual
104
+ uhb brain status # RAG (8200), SiYuan (6806), MARI (55553)
105
+ uhb brain search "Bridge MARI" # ask your own notes, through the local RAG
106
+ ```
107
+
108
+ The `brain` group reads the local second brain when it exists — RAG on 8200, SiYuan
109
+ on 6806, the MARI bridge on 55553 — with 2 s probes and a 30 s search budget. A
110
+ stopped service is reported as `○`, not as an exception; a failed search raises,
111
+ because an empty list would read as "your notes have nothing".
112
+
113
+ `uhb run` also serves a small control panel, because a backend nobody can see is
114
+ a backend nobody trusts:
115
+
116
+ | Endpoint | What it answers |
117
+ | --- | --- |
118
+ | `GET /` | panel: a discreet **9+ UHB** badge (green/orange dot), a modal with the six commands that matter, and the manual download |
119
+ | `GET /status` | `connected`, `version`, `mode`, `harness`, providers with `key_loaded`, modes, docs URLs |
120
+ | `GET /docs/manuel.pdf` · `/docs/manual.pdf` | the manual (French · English), `application/pdf`, as an attachment |
121
+
122
+ The panel is one self-contained HTML page: no CDN, no font download, no build step.
123
+ Embed the whole page in a dashboard with an iframe if you want the status view.
124
+ For the badge *and* its modal, copy the markup instead: the modal is a fixed
125
+ overlay sized to its viewport, so an iframe the size of the badge clips it.
126
+
127
+ `uhb status` never prints a credential: a provider's key is reported as `loaded` or
128
+ `MISSING`, next to the variable **name**. `uhb config set` refuses any key whose
129
+ last segment looks like a secret — a token value never enters a file.
130
+
131
+ `uhb update` has no `--force` and no `reset`. It fetches, refuses a dirty tree,
132
+ fast-forwards only, and reports the commit before and after. If the branch has
133
+ diverged it stops and says so.
134
+
135
+ ---
136
+
137
+ ## Full stack, or just the brain
138
+
139
+ UHB works alone: modes, router, write gate, wiki, panel. The second brain (a local RAG,
140
+ a SiYuan workspace, the MARI bridge) is **optional**, and the repository ships the code
141
+ and the recipe rather than the binaries:
142
+
143
+ ```bash
144
+ docker compose up -d # uhb + rag + siyuan, every port bound to 127.0.0.1
145
+ docker compose up -d uhb siyuan # skip the RAG: no index, no torch download
146
+ ```
147
+
148
+ Without Docker:
149
+
150
+ ```bash
151
+ ./install_services.sh # or install_services.ps1 on Windows
152
+ # downloads SiYuan, verifies the published sha256
153
+ python -m uhb.services.install --with rag --yes # ~1.5 GB (torch, faiss, flashrank)
154
+ python -m uhb.services.install --with mari --mari-src <path to the .NET project>
155
+ uhb brain start --dry-run # what would be launched, and from where
156
+ uhb brain start # launches what is installed, prints pids and urls
157
+ ```
158
+
159
+ Nothing downloaded lands in git: `uhb/bin/` is ignored and never packaged. One honest
160
+ limit remains — **the RAG index is yours to build**: it is made of your notes, a fresh
161
+ clone has an empty brain, and `/sante` says `fragments: 0` rather than inventing results.
162
+
163
+ ### Repository layout
164
+
165
+ The monorepo carries our own code; heavy or third-party trees are fetched, not copied:
166
+
167
+ ```
168
+ uhb/services/rag/ our RAG service (FastAPI + faiss), ~50 KB of code
169
+ uhb/services/mari/bridge/ our MARI bridge (5 C# files, 18 KB)
170
+ uhb/services/mari/framework/ git submodule -> Antoine-Thomas/mari (MIT, fork of kaonomi-net/mari)
171
+ uhb/bin/ DOWNLOADED at install time, never committed, never packaged
172
+ docker/ Dockerfile.uhb, Dockerfile.rag
173
+ docker-compose.yml uhb + rag + siyuan, every port on 127.0.0.1
174
+ install_services.sh|.ps1 thin wrappers -> python -m uhb.services.install
175
+ ```
176
+
177
+ Clone with the submodule, or the MARI build will refuse with a message saying so:
178
+
179
+ ```bash
180
+ git clone --recurse-submodules https://github.com/Antoine-Thomas/UHB-Universal-Harness-Backend.git
181
+ # already cloned? git submodule update --init
182
+ ```
183
+
184
+ `install.sh --full` chains everything: the package, SiYuan (sha256-verified download),
185
+ the RAG dependencies, and the MARI bridge built from `uhb/services/mari/bridge`.
186
+
187
+ ## Install
188
+
189
+ Requires Python 3.11+. Nothing else (httpx is the only runtime dependency).
190
+
191
+ ```bash
192
+ pip install uhb-harness # the CLI + the panel + the embedded manual
193
+ pip install "uhb-harness[yaml]" # additionally enables `uhb config set`
194
+ ```
195
+
196
+ From a checkout:
197
+
198
+ ```bash
199
+ git clone https://github.com/Antoine-Thomas/UHB-Universal-Harness-Backend.git
200
+ cd UHB-Universal-Harness-Backend
201
+ ./install.sh # creates .venv, installs, verifies `uhb --version`
202
+ ./install.sh --with-yaml # additionally enables `uhb config set`
203
+ ```
204
+
205
+ Or, by hand:
206
+
207
+ ```bash
208
+ python -m venv .venv && . .venv/bin/activate
209
+ pip install -e ".[yaml]"
210
+ uhb --version
211
+ ```
212
+
213
+ The installer never uses `sudo`, never writes outside the checkout, and is a no-op
214
+ reinstall on a second run. `./install.sh --dry-run` prints the commands and changes
215
+ nothing.
216
+
217
+ ---
218
+
219
+ ## Architecture
220
+
221
+ ```
222
+ control plane (Harness Maxxing · OpenClaw · Hermes · your CLI)
223
+ │
224
+ ▼
225
+ ┌─────────────── UHB ────────────────────────────────────────────┐
226
+ │ modes what a turn may read, write, and which tools exist │
227
+ │ router classify → plan a tiered chain → execute w/ fallback│
228
+ │ guard check every planned write, then write │
229
+ │ wiki raw sources → compiled pages → schema + lint │
230
+ │ entrypoints cli · HTTP (OpenAI + Anthropic) · fake `claude` │
231
+ └────────────────────────┬───────────────────────────────────────┘
232
+ ▼
233
+ providers: DeepSeek · Kimi · OpenAI · Anthropic · local Ollama
234
+ ```
235
+
236
+ Fallback rules, in code and in the report: a remote provider whose key variable is
237
+ absent is **skipped, not called**; a provider that raises is recorded and skipped
238
+ (the exception *class* is kept, never a payload); if every candidate fails the call
239
+ raises with its attempts — UHB never returns an empty string that reads like an
240
+ answer.
241
+
242
+ Same lever applied to thinking models: a routed call sends
243
+ `reasoning_effort: none` by default, because extraction is not deliberation.
244
+ Measured on `qwen3:8b`: **0.7 s instead of 7.1 s** for a one-word reply.
245
+
246
+ ---
247
+
248
+ ## Verified end to end
249
+
250
+ Run against the real thing on the reference machine, not against a mock:
251
+
252
+ | What | Evidence |
253
+ | --- | --- |
254
+ | `uhb run` + a real harness | `uhb run --mode chat --port 8099`, then `POST /v1/chat/completions` → `{"model": "deepseek/deepseek-flash", "content": "salut"}`, ~5.6 s round trip through the `hermes` subprocess |
255
+ | `uhb execute` | `uhb execute "Réponds uniquement: bonjour" --mode chat` → `bonjour`, exit 0, 5.6 s |
256
+ | the routed model reaches the harness | the child's argv is asserted to contain `--provider deepseek -m deepseek-flash` |
257
+ | a compile with a paid model | `deepseek-flash` on a three-sentence note: **4 pages, 8 links, `lint ok = True`**, one call, 4.44 s |
258
+ | the same compile, free model | `ollama/qwen3:8b`: 1 page, 1 orphan, 2 dead links, 7.75 s — the routing works, the quality does not |
259
+ | a vendor failure | every candidate answering 500 → `AllProvidersFailed`, three recorded attempts, nothing written |
260
+
261
+ Two of those runs also corrected the product: the DeepSeek model ids in
262
+ `config.example.yaml` (`deepseek-v4.1-*`) **do not exist** — the vendor serves
263
+ `deepseek-flash` and `deepseek-v4-pro`, discovered by calling `/v1/models` — and
264
+ the adapter kept a stray `\r` on every token read from a Windows subprocess.
265
+
266
+ ## Adjacent layer: the Council and its Oracle
267
+
268
+ Some harnesses run an adversarial deliberation layer above the model — Hermes Agent can bind
269
+ `hermes-council` (MIT, `Ridwannurudeen/hermes-council`), an MCP server with five personas:
270
+ advocate, skeptic, **oracle**, contrarian, arbiter. The Oracle is the empirical voice: it argues
271
+ from base rates and statistics rather than from principle.
272
+
273
+ It is worth being exact about the relationship, because "compatible" would be the easy word and
274
+ the wrong one:
275
+
276
+ | | The Council | UHB |
277
+ | --- | --- | --- |
278
+ | Speaks | MCP (stdio), bound to a harness profile | HTTP + CLI, bound to a harness |
279
+ | Judges | whether an irreversible **action** should happen | whether a **write** may happen, and which model answers |
280
+ | Latency | ~109 s measured for one gate (3 personas, free model) | 0.013 ms to decide, then the model's own time |
281
+ | Verdict | consultative | fail-closed, enforced before the first byte is written |
282
+
283
+ **UHB has no MCP client, and this is not an integration.** The Council lives in the harness; UHB
284
+ lives underneath it. Pointed at the same harness, they cover different questions — the Council
285
+ asking "should this be done at all", UHB answering "this mode may not write there, and this model
286
+ is the one that answers". Nothing in this repository talks to `hermes-council`, and the ~109 s
287
+ gate belongs nowhere near an interactive path.
288
+
289
+ ## Limits, stated plainly
290
+
291
+ - **The compile was measured once.** `COMPILE_COMPLEXITY = "standard"` produced four
292
+ clean pages for a three-sentence note; a 50-source ingest is untested, and that
293
+ constant is the first knob to re-examine.
294
+ - **Only DeepSeek is verified against its vendor.** Kimi, OpenAI and Anthropic
295
+ entries have never been resolved against a real catalog — treat them as declared,
296
+ not proven.
297
+ - **`uhb update` does not restart anything.** UHB ships no supervisor; systemd, Task
298
+ Scheduler or your terminal owns the process. Killing a PID read from a file is a
299
+ worse default than saying "restart it yourself".
300
+ - **The Harness Maxxing and OpenClaw adapters are skeletons.** They declare the
301
+ contract; they have not been run against those products yet.
302
+ - **A harness running as a subprocess is outside the gate.** UHB gates its own
303
+ writes (`wiki`, `memory`); what `hermes` does on its own filesystem is not
304
+ something a library can police.
305
+
306
+ ---
307
+
308
+ ## Roadmap
309
+
310
+ - ☑ 1.1 wiki: three layers (`raw/`, `wiki/`, schema), `ingest` / `query` / `lint`
311
+ - ☑ 1.2 modes: four profiles, fail-closed permissions, prompt envelope
312
+ - ☑ 1.3 router: classification, tiered chain, fallback, measured latency
313
+ - ☑ 1.4 wiring: gate on every write path, router in the pipeline and entrypoints
314
+ - ☑ 2 packaging: CLI, installer, this README
315
+ - ☐ 3 distribution: public release of the HUB (licence decision: MIT spec + Fair
316
+ Source implementation), then the adapters against the real products
317
+
318
+ ## Tests
319
+
320
+ `pytest uhb/tests -q` — **197 passed, 1 skipped**, in 3.8 s, no network: the model
321
+ is injected, git is injected, and the one end-to-end test is opt-in against a local
322
+ Ollama (`UHB_OLLAMA_E2E=1`).
323
+
324
+ Four security greps run before every commit, on every touched file: no key prefix,
325
+ no credential value, no absolute path, no environment dump. Current result: 0.
326
+
327
+ ## Licence
328
+
329
+ Two licences, one per layer — the split is deliberate, and the boundary is the
330
+ path:
331
+
332
+ | Path | Licence | What it means |
333
+ | --- | --- | --- |
334
+ | `SPEC.md`, `ARCHITECTURE.md`, `ADAPTERS.md`, `NON-GOALS.md`, `README.md` | **MIT** (`LICENSE`) | implement the contract freely, in any product, commercial or not |
335
+ | `uhb/` — the implementation | **Fair Source, FSL-1.1-MIT** (`LICENSE-IMPLEMENTATION.md`) | read it, run it, modify it, use it commercially; you may not resell it as a competing hosted service. Each release becomes MIT two years after publication |
336
+
337
+ The contract is open so harnesses can adopt it without a legal conversation. The
338
+ implementation is Fair Source so the work can be funded. Both texts ship inside
339
+ the package: no one has to guess which one covers what.
340
+
341
+ Built by Thomas Leroyer (Searching Murphy).
@@ -0,0 +1,323 @@
1
+ # UHB — Universal Harness Backend
2
+
3
+ > **Your harness gives you the body — the UI, the sessions, the automations.
4
+ > UHB gives it the brain — the memory, the routing, the absolute permissions.**
5
+
6
+ UHB is not another agent. It is the execution backend underneath one: modes that
7
+ decide what a turn may do, a router that decides which model answers, a write gate
8
+ that refuses before it touches a file, and a compiled wiki that remembers.
9
+
10
+ Harness Maxxing, OpenClaw, Hermes Agent or your own CLI plug into it — and stop
11
+ caring which vendor's model their harness was written against.
12
+
13
+ Status: **0.1.0** — wiki, modes, router and write gate are in service; this
14
+ release adds the CLI, the panel, the embedded manual and the packaging. Every
15
+ number below was measured on the reference machine and is reproducible with the
16
+ command next to it.
17
+
18
+ Distribution name: `uhb-harness` on PyPI (`uhb` is taken by an unrelated
19
+ project); the command is `uhb`.
20
+
21
+ ---
22
+
23
+ ## Why this exists
24
+
25
+ ### 1. Routing that costs nothing
26
+
27
+ The intent router classifies a request and plans a model chain in **0.013 ms**
28
+ (p50) — no model call, no tokens. It is deterministic: same request, same policy,
29
+ same chain, every time. You can inspect the decision before spending anything.
30
+
31
+ | Measurement | Value | How |
32
+ | --- | --- | --- |
33
+ | `route()` | p50 0.013 ms · p95 0.020 ms · p99 0.026 ms | 2000 calls, `perf_counter` |
34
+ | `classify()` | p50 0.007 ms | 2000 calls |
35
+ | routing policy load | 0.50 ms cold · 0.22 ms cached | 5 models, 3 tiers |
36
+
37
+ ### 2. Fail-closed permissions, not suggestions
38
+
39
+ Four execution modes, each a JSON file: `chat`, `dev`, `observe`, `review`. A
40
+ mode's write scope is a list of globs; **`deny` wins over everything**, an unknown
41
+ key in a profile is a load error, and a write path with no mode raises
42
+ `ModeRequired` instead of assuming one.
43
+
44
+ ```bash
45
+ uhb modes show review # write: [] — the agent physically cannot edit your files
46
+ ```
47
+
48
+ A refused compile leaves every byte of the tree exactly as it was: all planned
49
+ writes are checked first, then written. Measured in the test suite by comparing a
50
+ byte-level snapshot of the whole tree before and after a refused write.
51
+
52
+ ### 3. No vendor lock-in
53
+
54
+ | What | Where it lives |
55
+ | --- | --- |
56
+ | How to reach a provider | `uhb/config.yaml` (protocol, base URL, key **variable name**) |
57
+ | When to pick a model | `router/catalog.json` (tier, supported complexity) |
58
+ | What a turn may do | `hub/modes/profiles/*.json` |
59
+
60
+ Adding a vendor means one entry in the provider table. Adding a model to the policy
61
+ means one line in the catalog. A catalog entry no provider can serve is refused at
62
+ load time — a route pointing nowhere is worse than no route.
63
+
64
+ ---
65
+
66
+ ## The CLI
67
+
68
+ ```bash
69
+ uhb status # version, git state, providers, policy, modes
70
+ uhb status --json # same, machine-readable
71
+ uhb modes list # name, kind, tier, write scope, turn budget
72
+ uhb modes show dev # the whole profile
73
+ uhb config list # every effective key (defaults included)
74
+ uhb config get routing.default
75
+ uhb config set routing.default deepseek/deepseek-flash
76
+ uhb run --mode dev --port 8080 # HTTP entrypoint (OpenAI + Anthropic shapes)
77
+ uhb execute --mode dev "..." # one turn through the configured harness
78
+ uhb update --dry-run # fetch and report; nothing moves
79
+ uhb update # git pull --ff-only, then prove it
80
+ uhb docs # path and URL of the embedded manual
81
+ uhb brain status # RAG (8200), SiYuan (6806), MARI (55553)
82
+ uhb brain search "Bridge MARI" # ask your own notes, through the local RAG
83
+ ```
84
+
85
+ The `brain` group reads the local second brain when it exists — RAG on 8200, SiYuan
86
+ on 6806, the MARI bridge on 55553 — with 2 s probes and a 30 s search budget. A
87
+ stopped service is reported as `○`, not as an exception; a failed search raises,
88
+ because an empty list would read as "your notes have nothing".
89
+
90
+ `uhb run` also serves a small control panel, because a backend nobody can see is
91
+ a backend nobody trusts:
92
+
93
+ | Endpoint | What it answers |
94
+ | --- | --- |
95
+ | `GET /` | panel: a discreet **9+ UHB** badge (green/orange dot), a modal with the six commands that matter, and the manual download |
96
+ | `GET /status` | `connected`, `version`, `mode`, `harness`, providers with `key_loaded`, modes, docs URLs |
97
+ | `GET /docs/manuel.pdf` · `/docs/manual.pdf` | the manual (French · English), `application/pdf`, as an attachment |
98
+
99
+ The panel is one self-contained HTML page: no CDN, no font download, no build step.
100
+ Embed the whole page in a dashboard with an iframe if you want the status view.
101
+ For the badge *and* its modal, copy the markup instead: the modal is a fixed
102
+ overlay sized to its viewport, so an iframe the size of the badge clips it.
103
+
104
+ `uhb status` never prints a credential: a provider's key is reported as `loaded` or
105
+ `MISSING`, next to the variable **name**. `uhb config set` refuses any key whose
106
+ last segment looks like a secret — a token value never enters a file.
107
+
108
+ `uhb update` has no `--force` and no `reset`. It fetches, refuses a dirty tree,
109
+ fast-forwards only, and reports the commit before and after. If the branch has
110
+ diverged it stops and says so.
111
+
112
+ ---
113
+
114
+ ## Full stack, or just the brain
115
+
116
+ UHB works alone: modes, router, write gate, wiki, panel. The second brain (a local RAG,
117
+ a SiYuan workspace, the MARI bridge) is **optional**, and the repository ships the code
118
+ and the recipe rather than the binaries:
119
+
120
+ ```bash
121
+ docker compose up -d # uhb + rag + siyuan, every port bound to 127.0.0.1
122
+ docker compose up -d uhb siyuan # skip the RAG: no index, no torch download
123
+ ```
124
+
125
+ Without Docker:
126
+
127
+ ```bash
128
+ ./install_services.sh # or install_services.ps1 on Windows
129
+ # downloads SiYuan, verifies the published sha256
130
+ python -m uhb.services.install --with rag --yes # ~1.5 GB (torch, faiss, flashrank)
131
+ python -m uhb.services.install --with mari --mari-src <path to the .NET project>
132
+ uhb brain start --dry-run # what would be launched, and from where
133
+ uhb brain start # launches what is installed, prints pids and urls
134
+ ```
135
+
136
+ Nothing downloaded lands in git: `uhb/bin/` is ignored and never packaged. One honest
137
+ limit remains — **the RAG index is yours to build**: it is made of your notes, a fresh
138
+ clone has an empty brain, and `/sante` says `fragments: 0` rather than inventing results.
139
+
140
+ ### Repository layout
141
+
142
+ The monorepo carries our own code; heavy or third-party trees are fetched, not copied:
143
+
144
+ ```
145
+ uhb/services/rag/ our RAG service (FastAPI + faiss), ~50 KB of code
146
+ uhb/services/mari/bridge/ our MARI bridge (5 C# files, 18 KB)
147
+ uhb/services/mari/framework/ git submodule -> Antoine-Thomas/mari (MIT, fork of kaonomi-net/mari)
148
+ uhb/bin/ DOWNLOADED at install time, never committed, never packaged
149
+ docker/ Dockerfile.uhb, Dockerfile.rag
150
+ docker-compose.yml uhb + rag + siyuan, every port on 127.0.0.1
151
+ install_services.sh|.ps1 thin wrappers -> python -m uhb.services.install
152
+ ```
153
+
154
+ Clone with the submodule, or the MARI build will refuse with a message saying so:
155
+
156
+ ```bash
157
+ # one shot (Linux, macOS, and Windows from cmd or PowerShell)
158
+ git clone --recurse-submodules https://github.com/Antoine-Thomas/UHB-Universal-Harness-Backend.git
159
+
160
+ # Windows + Git Bash: the one-shot form can fail with "not a git repository: /tmp/...",
161
+ # a path quirk of the submodule helper, not of this repository. Two steps work:
162
+ git clone https://github.com/Antoine-Thomas/UHB-Universal-Harness-Backend.git
163
+ cd UHB-Universal-Harness-Backend && git submodule update --init
164
+ ```
165
+
166
+ `install.sh --full` chains everything: the package, SiYuan (sha256-verified download),
167
+ the RAG dependencies, and the MARI bridge built from `uhb/services/mari/bridge`.
168
+
169
+ ## Install
170
+
171
+ Requires Python 3.11+. Nothing else (httpx is the only runtime dependency).
172
+
173
+ ```bash
174
+ pip install uhb-harness # the CLI + the panel + the embedded manual
175
+ pip install "uhb-harness[yaml]" # additionally enables `uhb config set`
176
+ ```
177
+
178
+ From a checkout:
179
+
180
+ ```bash
181
+ git clone https://github.com/Antoine-Thomas/UHB-Universal-Harness-Backend.git
182
+ cd UHB-Universal-Harness-Backend
183
+ ./install.sh # creates .venv, installs, verifies `uhb --version`
184
+ ./install.sh --with-yaml # additionally enables `uhb config set`
185
+ ```
186
+
187
+ Or, by hand:
188
+
189
+ ```bash
190
+ python -m venv .venv && . .venv/bin/activate
191
+ pip install -e ".[yaml]"
192
+ uhb --version
193
+ ```
194
+
195
+ The installer never uses `sudo`, never writes outside the checkout, and is a no-op
196
+ reinstall on a second run. `./install.sh --dry-run` prints the commands and changes
197
+ nothing.
198
+
199
+ ---
200
+
201
+ ## Architecture
202
+
203
+ ```
204
+ control plane (Harness Maxxing · OpenClaw · Hermes · your CLI)
205
+ │
206
+ ▼
207
+ ┌─────────────── UHB ────────────────────────────────────────────┐
208
+ │ modes what a turn may read, write, and which tools exist │
209
+ │ router classify → plan a tiered chain → execute w/ fallback│
210
+ │ guard check every planned write, then write │
211
+ │ wiki raw sources → compiled pages → schema + lint │
212
+ │ entrypoints cli · HTTP (OpenAI + Anthropic) · fake `claude` │
213
+ └────────────────────────┬───────────────────────────────────────┘
214
+ ▼
215
+ providers: DeepSeek · Kimi · OpenAI · Anthropic · local Ollama
216
+ ```
217
+
218
+ Fallback rules, in code and in the report: a remote provider whose key variable is
219
+ absent is **skipped, not called**; a provider that raises is recorded and skipped
220
+ (the exception *class* is kept, never a payload); if every candidate fails the call
221
+ raises with its attempts — UHB never returns an empty string that reads like an
222
+ answer.
223
+
224
+ Same lever applied to thinking models: a routed call sends
225
+ `reasoning_effort: none` by default, because extraction is not deliberation.
226
+ Measured on `qwen3:8b`: **0.7 s instead of 7.1 s** for a one-word reply.
227
+
228
+ ---
229
+
230
+ ## Verified end to end
231
+
232
+ Run against the real thing on the reference machine, not against a mock:
233
+
234
+ | What | Evidence |
235
+ | --- | --- |
236
+ | `uhb run` + a real harness | `uhb run --mode chat --port 8099`, then `POST /v1/chat/completions` → `{"model": "deepseek/deepseek-flash", "content": "salut"}`, ~5.6 s round trip through the `hermes` subprocess |
237
+ | `uhb execute` | `uhb execute "Réponds uniquement: bonjour" --mode chat` → `bonjour`, exit 0, 5.6 s |
238
+ | the routed model reaches the harness | the child's argv is asserted to contain `--provider deepseek -m deepseek-flash` |
239
+ | a compile with a paid model | `deepseek-flash` on a three-sentence note: **4 pages, 8 links, `lint ok = True`**, one call, 4.44 s |
240
+ | the same compile, free model | `ollama/qwen3:8b`: 1 page, 1 orphan, 2 dead links, 7.75 s — the routing works, the quality does not |
241
+ | a vendor failure | every candidate answering 500 → `AllProvidersFailed`, three recorded attempts, nothing written |
242
+
243
+ Two of those runs also corrected the product: the DeepSeek model ids in
244
+ `config.example.yaml` (`deepseek-v4.1-*`) **do not exist** — the vendor serves
245
+ `deepseek-flash` and `deepseek-v4-pro`, discovered by calling `/v1/models` — and
246
+ the adapter kept a stray `\r` on every token read from a Windows subprocess.
247
+
248
+ ## Adjacent layer: the Council and its Oracle
249
+
250
+ Some harnesses run an adversarial deliberation layer above the model — Hermes Agent can bind
251
+ `hermes-council` (MIT, `Ridwannurudeen/hermes-council`), an MCP server with five personas:
252
+ advocate, skeptic, **oracle**, contrarian, arbiter. The Oracle is the empirical voice: it argues
253
+ from base rates and statistics rather than from principle.
254
+
255
+ It is worth being exact about the relationship, because "compatible" would be the easy word and
256
+ the wrong one:
257
+
258
+ | | The Council | UHB |
259
+ | --- | --- | --- |
260
+ | Speaks | MCP (stdio), bound to a harness profile | HTTP + CLI, bound to a harness |
261
+ | Judges | whether an irreversible **action** should happen | whether a **write** may happen, and which model answers |
262
+ | Latency | ~109 s measured for one gate (3 personas, free model) | 0.013 ms to decide, then the model's own time |
263
+ | Verdict | consultative | fail-closed, enforced before the first byte is written |
264
+
265
+ **UHB has no MCP client, and this is not an integration.** The Council lives in the harness; UHB
266
+ lives underneath it. Pointed at the same harness, they cover different questions — the Council
267
+ asking "should this be done at all", UHB answering "this mode may not write there, and this model
268
+ is the one that answers". Nothing in this repository talks to `hermes-council`, and the ~109 s
269
+ gate belongs nowhere near an interactive path.
270
+
271
+ ## Limits, stated plainly
272
+
273
+ - **The compile was measured once.** `COMPILE_COMPLEXITY = "standard"` produced four
274
+ clean pages for a three-sentence note; a 50-source ingest is untested, and that
275
+ constant is the first knob to re-examine.
276
+ - **Only DeepSeek is verified against its vendor.** Kimi, OpenAI and Anthropic
277
+ entries have never been resolved against a real catalog — treat them as declared,
278
+ not proven.
279
+ - **`uhb update` does not restart anything.** UHB ships no supervisor; systemd, Task
280
+ Scheduler or your terminal owns the process. Killing a PID read from a file is a
281
+ worse default than saying "restart it yourself".
282
+ - **The Harness Maxxing and OpenClaw adapters are skeletons.** They declare the
283
+ contract; they have not been run against those products yet.
284
+ - **A harness running as a subprocess is outside the gate.** UHB gates its own
285
+ writes (`wiki`, `memory`); what `hermes` does on its own filesystem is not
286
+ something a library can police.
287
+
288
+ ---
289
+
290
+ ## Roadmap
291
+
292
+ - ☑ 1.1 wiki: three layers (`raw/`, `wiki/`, schema), `ingest` / `query` / `lint`
293
+ - ☑ 1.2 modes: four profiles, fail-closed permissions, prompt envelope
294
+ - ☑ 1.3 router: classification, tiered chain, fallback, measured latency
295
+ - ☑ 1.4 wiring: gate on every write path, router in the pipeline and entrypoints
296
+ - ☑ 2 packaging: CLI, installer, this README
297
+ - ☐ 3 distribution: public release of the HUB (licence decision: MIT spec + Fair
298
+ Source implementation), then the adapters against the real products
299
+
300
+ ## Tests
301
+
302
+ `pytest uhb/tests -q` — **197 passed, 1 skipped**, in 3.8 s, no network: the model
303
+ is injected, git is injected, and the one end-to-end test is opt-in against a local
304
+ Ollama (`UHB_OLLAMA_E2E=1`).
305
+
306
+ Four security greps run before every commit, on every touched file: no key prefix,
307
+ no credential value, no absolute path, no environment dump. Current result: 0.
308
+
309
+ ## Licence
310
+
311
+ Two licences, one per layer — the split is deliberate, and the boundary is the
312
+ path:
313
+
314
+ | Path | Licence | What it means |
315
+ | --- | --- | --- |
316
+ | `SPEC.md`, `ARCHITECTURE.md`, `ADAPTERS.md`, `NON-GOALS.md`, `README.md` | **MIT** (`LICENSE`) | implement the contract freely, in any product, commercial or not |
317
+ | `uhb/` — the implementation | **Fair Source, FSL-1.1-MIT** (`LICENSE-IMPLEMENTATION.md`) | read it, run it, modify it, use it commercially; you may not resell it as a competing hosted service. Each release becomes MIT two years after publication |
318
+
319
+ The contract is open so harnesses can adopt it without a legal conversation. The
320
+ implementation is Fair Source so the work can be funded. Both texts ship inside
321
+ the package: no one has to guess which one covers what.
322
+
323
+ Built by Thomas Leroyer (Searching Murphy).