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.
- uhb_harness-0.1.0/PKG-INFO +341 -0
- uhb_harness-0.1.0/README.md +323 -0
- uhb_harness-0.1.0/pyproject.toml +66 -0
- uhb_harness-0.1.0/setup.cfg +4 -0
- uhb_harness-0.1.0/uhb/LICENSE +18 -0
- uhb_harness-0.1.0/uhb/LICENSE-IMPLEMENTATION.md +112 -0
- uhb_harness-0.1.0/uhb/README.md +318 -0
- uhb_harness-0.1.0/uhb/__init__.py +34 -0
- uhb_harness-0.1.0/uhb/adapters/__init__.py +6 -0
- uhb_harness-0.1.0/uhb/adapters/base.py +81 -0
- uhb_harness-0.1.0/uhb/adapters/harness_maxxing.py +68 -0
- uhb_harness-0.1.0/uhb/adapters/hermes.py +154 -0
- uhb_harness-0.1.0/uhb/adapters/openclaw.py +61 -0
- uhb_harness-0.1.0/uhb/adapters/second_brain.py +296 -0
- uhb_harness-0.1.0/uhb/config.example.yaml +45 -0
- uhb_harness-0.1.0/uhb/core/__init__.py +36 -0
- uhb_harness-0.1.0/uhb/core/contract.py +73 -0
- uhb_harness-0.1.0/uhb/core/executor.py +115 -0
- uhb_harness-0.1.0/uhb/core/health.py +86 -0
- uhb_harness-0.1.0/uhb/core/memory.py +109 -0
- uhb_harness-0.1.0/uhb/core/router.py +155 -0
- uhb_harness-0.1.0/uhb/core/skills.py +104 -0
- uhb_harness-0.1.0/uhb/core/types.py +120 -0
- uhb_harness-0.1.0/uhb/docs/MANUEL_UHB.md +316 -0
- uhb_harness-0.1.0/uhb/docs/MANUEL_UHB.pdf +0 -0
- uhb_harness-0.1.0/uhb/docs/MANUEL_UHB_EN.md +320 -0
- uhb_harness-0.1.0/uhb/docs/MANUEL_UHB_EN.pdf +0 -0
- uhb_harness-0.1.0/uhb/docs/UI_INTEGRATION.md +274 -0
- uhb_harness-0.1.0/uhb/entrypoints/__init__.py +9 -0
- uhb_harness-0.1.0/uhb/entrypoints/cli.py +496 -0
- uhb_harness-0.1.0/uhb/entrypoints/fake_claude.py +185 -0
- uhb_harness-0.1.0/uhb/entrypoints/panel.py +326 -0
- uhb_harness-0.1.0/uhb/entrypoints/server.py +301 -0
- uhb_harness-0.1.0/uhb/entrypoints/settings.py +143 -0
- uhb_harness-0.1.0/uhb/entrypoints/status.py +135 -0
- uhb_harness-0.1.0/uhb/entrypoints/update.py +136 -0
- uhb_harness-0.1.0/uhb/hub/__init__.py +27 -0
- uhb_harness-0.1.0/uhb/hub/guard.py +110 -0
- uhb_harness-0.1.0/uhb/hub/modes/__init__.py +357 -0
- uhb_harness-0.1.0/uhb/hub/modes/profiles/chat.json +18 -0
- uhb_harness-0.1.0/uhb/hub/modes/profiles/dev.json +19 -0
- uhb_harness-0.1.0/uhb/hub/modes/profiles/observe.json +18 -0
- uhb_harness-0.1.0/uhb/hub/modes/profiles/review.json +19 -0
- uhb_harness-0.1.0/uhb/hub/router/__init__.py +568 -0
- uhb_harness-0.1.0/uhb/hub/router/catalog.json +31 -0
- uhb_harness-0.1.0/uhb/hub/wiki/__init__.py +27 -0
- uhb_harness-0.1.0/uhb/hub/wiki/scripts/__init__.py +469 -0
- uhb_harness-0.1.0/uhb/hub/wiki/scripts/ingest.py +490 -0
- uhb_harness-0.1.0/uhb/hub/wiki/scripts/lint.py +297 -0
- uhb_harness-0.1.0/uhb/hub/wiki/scripts/query.py +209 -0
- uhb_harness-0.1.0/uhb/hub/wiki/wiki/contradictions.md +8 -0
- uhb_harness-0.1.0/uhb/hub/wiki/wiki/index.md +19 -0
- uhb_harness-0.1.0/uhb/hub/wiki/wiki/log.md +5 -0
- uhb_harness-0.1.0/uhb/hub/wiki/wiki/schema.md +71 -0
- uhb_harness-0.1.0/uhb/services/__init__.py +15 -0
- uhb_harness-0.1.0/uhb/services/install.py +284 -0
- uhb_harness-0.1.0/uhb/services/mari/README.md +33 -0
- uhb_harness-0.1.0/uhb/services/mari/bridge/Bridge.csproj +20 -0
- uhb_harness-0.1.0/uhb/services/mari/bridge/IJournalWriterVGrain.cs +15 -0
- uhb_harness-0.1.0/uhb/services/mari/bridge/IRemediationWriterVGrain.cs +15 -0
- uhb_harness-0.1.0/uhb/services/mari/bridge/JournalWriterVGrain.cs +26 -0
- uhb_harness-0.1.0/uhb/services/mari/bridge/Program.cs +152 -0
- uhb_harness-0.1.0/uhb/services/mari/bridge/RemediationWriterVGrain.cs +28 -0
- uhb_harness-0.1.0/uhb/services/mari/bridge/appsettings.Development.json +8 -0
- uhb_harness-0.1.0/uhb/services/mari/bridge/appsettings.json +17 -0
- uhb_harness-0.1.0/uhb/services/rag/README.md +57 -0
- uhb_harness-0.1.0/uhb/services/rag/chercher.py +335 -0
- uhb_harness-0.1.0/uhb/services/rag/requirements.txt +14 -0
- uhb_harness-0.1.0/uhb/services/rag/serveur_rag.py +190 -0
- uhb_harness-0.1.0/uhb/tests/__init__.py +5 -0
- uhb_harness-0.1.0/uhb/tests/test_cli.py +453 -0
- uhb_harness-0.1.0/uhb/tests/test_contract.py +98 -0
- uhb_harness-0.1.0/uhb/tests/test_e2e_harness.py +259 -0
- uhb_harness-0.1.0/uhb/tests/test_entrypoints.py +147 -0
- uhb_harness-0.1.0/uhb/tests/test_hub_gate.py +292 -0
- uhb_harness-0.1.0/uhb/tests/test_hub_modes.py +246 -0
- uhb_harness-0.1.0/uhb/tests/test_hub_router.py +414 -0
- uhb_harness-0.1.0/uhb/tests/test_hub_wiki.py +486 -0
- uhb_harness-0.1.0/uhb/tests/test_hub_wiring.py +302 -0
- uhb_harness-0.1.0/uhb/tests/test_memory.py +120 -0
- uhb_harness-0.1.0/uhb/tests/test_router.py +88 -0
- uhb_harness-0.1.0/uhb/tests/test_second_brain.py +315 -0
- uhb_harness-0.1.0/uhb/tests/test_services.py +270 -0
- uhb_harness-0.1.0/uhb_harness.egg-info/PKG-INFO +341 -0
- uhb_harness-0.1.0/uhb_harness.egg-info/SOURCES.txt +87 -0
- uhb_harness-0.1.0/uhb_harness.egg-info/dependency_links.txt +1 -0
- uhb_harness-0.1.0/uhb_harness.egg-info/entry_points.txt +2 -0
- uhb_harness-0.1.0/uhb_harness.egg-info/requires.txt +8 -0
- 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).
|