willows-grove 0.9.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.
- willows_grove-0.9.0/.env.example +41 -0
- willows_grove-0.9.0/.gitignore +16 -0
- willows_grove-0.9.0/CHANGELOG.md +30 -0
- willows_grove-0.9.0/CLAUDE.md +77 -0
- willows_grove-0.9.0/PKG-INFO +131 -0
- willows_grove-0.9.0/README.md +96 -0
- willows_grove-0.9.0/bridge/__init__.py +1 -0
- willows_grove-0.9.0/bridge/__main__.py +112 -0
- willows_grove-0.9.0/bridge/app.py +311 -0
- willows_grove-0.9.0/bridge/matrix.py +109 -0
- willows_grove-0.9.0/bridge/registration.yaml +24 -0
- willows_grove-0.9.0/bridge/store.py +69 -0
- willows_grove-0.9.0/deploy/grove-mcp-serve.service.template +35 -0
- willows_grove-0.9.0/docs/INVARIANTS.md +478 -0
- willows_grove-0.9.0/docs/OPS_RUNBOOK.md +236 -0
- willows_grove-0.9.0/docs/audits/loki-swarm-measurement.md +261 -0
- willows_grove-0.9.0/docs/audits/loki-swarm-metadata.md +390 -0
- willows_grove-0.9.0/docs/audits/loki-swarm-raw.json +1047 -0
- willows_grove-0.9.0/docs/audits/loki-v0.9-audit.md +598 -0
- willows_grove-0.9.0/docs/design/autonomous-continuity.md +357 -0
- willows_grove-0.9.0/docs/design/pr14-carryovers.md +405 -0
- willows_grove-0.9.0/docs/design/u2u-security-limits.md +63 -0
- willows_grove-0.9.0/docs/design/watcher-e2e-notes.md +76 -0
- willows_grove-0.9.0/grove/__init__.py +1 -0
- willows_grove-0.9.0/grove/envelope_reader.py +235 -0
- willows_grove-0.9.0/grove/errors.py +63 -0
- willows_grove-0.9.0/grove/fleet_presence.py +107 -0
- willows_grove-0.9.0/grove/journal_reader.py +311 -0
- willows_grove-0.9.0/grove/journal_writer.py +211 -0
- willows_grove-0.9.0/grove/kart_reader.py +303 -0
- willows_grove-0.9.0/grove/mcp_auth.py +454 -0
- willows_grove-0.9.0/grove/mcp_local.py +1284 -0
- willows_grove-0.9.0/grove/nestor_client.py +376 -0
- willows_grove-0.9.0/grove/persona_roster.py +317 -0
- willows_grove-0.9.0/grove/resident_watcher.py +624 -0
- willows_grove-0.9.0/grove/seed_html.py +545 -0
- willows_grove-0.9.0/grove/seed_reader.py +254 -0
- willows_grove-0.9.0/grove_db.py +831 -0
- willows_grove-0.9.0/grove_html.py +302 -0
- willows_grove-0.9.0/grove_reader.py +1361 -0
- willows_grove-0.9.0/grove_serve.py +538 -0
- willows_grove-0.9.0/panes/__init__.py +5 -0
- willows_grove-0.9.0/panes/chat_admin.py +38 -0
- willows_grove-0.9.0/pyproject.toml +132 -0
- willows_grove-0.9.0/requirements.txt +26 -0
- willows_grove-0.9.0/run_mcp.sh +40 -0
- willows_grove-0.9.0/safe-app-manifest.json +27 -0
- willows_grove-0.9.0/schema.sql +175 -0
- willows_grove-0.9.0/scripts/check_docs_drift.py +229 -0
- willows_grove-0.9.0/scripts/check_persona_provenance.py +171 -0
- willows_grove-0.9.0/scripts/check_ratification.py +130 -0
- willows_grove-0.9.0/scripts/ci-security-grep.allowlist +21 -0
- willows_grove-0.9.0/scripts/ci-security-grep.sh +137 -0
- willows_grove-0.9.0/scripts/grove-serve +109 -0
- willows_grove-0.9.0/scripts/mcp_entry_toggle.py +47 -0
- willows_grove-0.9.0/scripts/run_test_dir_or_fail.sh +60 -0
- willows_grove-0.9.0/tests/__init__.py +0 -0
- willows_grove-0.9.0/tests/e2e/README.md +72 -0
- willows_grove-0.9.0/tests/e2e/grove-served-page.spec.js +420 -0
- willows_grove-0.9.0/tests/e2e/seed-canon.spec.js +175 -0
- willows_grove-0.9.0/tests/e2e/three-state-affordances.spec.js +310 -0
- willows_grove-0.9.0/tests/e2e_ollama/__init__.py +3 -0
- willows_grove-0.9.0/tests/e2e_ollama/conftest.py +438 -0
- willows_grove-0.9.0/tests/e2e_ollama/test_watcher_ollama_e2e.py +210 -0
- willows_grove-0.9.0/tests/e2e_ollama/test_watcher_ollama_readiness.py +67 -0
- willows_grove-0.9.0/tests/e2e_willow_mcp/conftest.py +169 -0
- willows_grove-0.9.0/tests/e2e_willow_mcp/mock_willow_mcp.py +218 -0
- willows_grove-0.9.0/tests/e2e_willow_mcp/test_journal_roundtrip.py +167 -0
- willows_grove-0.9.0/tests/e2e_willow_mcp/test_watcher_chat_readback_flow.py +114 -0
- willows_grove-0.9.0/tests/regression/screenshots/README.md +40 -0
- willows_grove-0.9.0/tests/regression/screenshots/seed/1.png +0 -0
- willows_grove-0.9.0/tests/regression/screenshots/seed/2.png +0 -0
- willows_grove-0.9.0/tests/regression/screenshots/seed/3.png +0 -0
- willows_grove-0.9.0/tests/regression/screenshots/seed/4.png +0 -0
- willows_grove-0.9.0/tests/regression/screenshots/seed/5.png +0 -0
- willows_grove-0.9.0/tests/regression/screenshots/seed/6.png +0 -0
- willows_grove-0.9.0/tests/test_ci_hashfiles_guards_removed.py +196 -0
- willows_grove-0.9.0/tests/test_ci_playwright_install_command.py +64 -0
- willows_grove-0.9.0/tests/test_ci_security_grep_docstring_matches_pattern.py +95 -0
- willows_grove-0.9.0/tests/test_claude_md_honesty.py +123 -0
- willows_grove-0.9.0/tests/test_docs_drift_check.py +187 -0
- willows_grove-0.9.0/tests/test_docs_no_dead_envvar_references.py +41 -0
- willows_grove-0.9.0/tests/test_e2e_ollama_ci_fails_loud.py +155 -0
- willows_grove-0.9.0/tests/test_envelope_reader.py +246 -0
- willows_grove-0.9.0/tests/test_fleet_presence.py +102 -0
- willows_grove-0.9.0/tests/test_fleet_presence_unreachable.py +76 -0
- willows_grove-0.9.0/tests/test_frank_ledger_error_surfaces.py +126 -0
- willows_grove-0.9.0/tests/test_grove_approval_page.py +315 -0
- willows_grove-0.9.0/tests/test_grove_chat_render.py +106 -0
- willows_grove-0.9.0/tests/test_grove_db_cursor_unreachable.py +106 -0
- willows_grove-0.9.0/tests/test_grove_db_timeouts.py +142 -0
- willows_grove-0.9.0/tests/test_grove_html_boot_wire.py +105 -0
- willows_grove-0.9.0/tests/test_grove_html_envelope_and_listener.py +98 -0
- willows_grove-0.9.0/tests/test_grove_html_no_hardcoded_status.py +83 -0
- willows_grove-0.9.0/tests/test_grove_html_refusal_boot.py +84 -0
- willows_grove-0.9.0/tests/test_grove_lens_switch.py +58 -0
- willows_grove-0.9.0/tests/test_grove_reader_error_redaction.py +150 -0
- willows_grove-0.9.0/tests/test_grove_reader_unreachable.py +156 -0
- willows_grove-0.9.0/tests/test_grove_rename_soil_partial_failure.py +109 -0
- willows_grove-0.9.0/tests/test_grove_serve.py +107 -0
- willows_grove-0.9.0/tests/test_grove_serve_dispatch.py +188 -0
- willows_grove-0.9.0/tests/test_grove_serve_env_validation.py +73 -0
- willows_grove-0.9.0/tests/test_grove_serve_envelopes.py +174 -0
- willows_grove-0.9.0/tests/test_grove_serve_journal.py +186 -0
- willows_grove-0.9.0/tests/test_grove_serve_journal_read.py +215 -0
- willows_grove-0.9.0/tests/test_grove_serve_nestor.py +291 -0
- willows_grove-0.9.0/tests/test_grove_serve_personas.py +214 -0
- willows_grove-0.9.0/tests/test_grove_serve_seed.py +125 -0
- willows_grove-0.9.0/tests/test_journal_reader.py +303 -0
- willows_grove-0.9.0/tests/test_journal_reader_unreachable.py +123 -0
- willows_grove-0.9.0/tests/test_journal_writer.py +174 -0
- willows_grove-0.9.0/tests/test_kart_reader.py +247 -0
- willows_grove-0.9.0/tests/test_mcp_auth.py +328 -0
- willows_grove-0.9.0/tests/test_mcp_local_oauth_hardening.py +198 -0
- willows_grove-0.9.0/tests/test_mcp_remote_tools.py +120 -0
- willows_grove-0.9.0/tests/test_mcp_serve_oauth_flow.py +275 -0
- willows_grove-0.9.0/tests/test_nestor_client.py +222 -0
- willows_grove-0.9.0/tests/test_nestor_client_unreachable.py +77 -0
- willows_grove-0.9.0/tests/test_panel_wiring.py +510 -0
- willows_grove-0.9.0/tests/test_panel_wiring_coverage.py +108 -0
- willows_grove-0.9.0/tests/test_pending_ttl_hard_ceiling.py +99 -0
- willows_grove-0.9.0/tests/test_persona_provenance_check.py +154 -0
- willows_grove-0.9.0/tests/test_persona_registry_inline_shim_opt_in.py +220 -0
- willows_grove-0.9.0/tests/test_persona_roster.py +287 -0
- willows_grove-0.9.0/tests/test_persona_roster_unreachable.py +216 -0
- willows_grove-0.9.0/tests/test_ratification_check.py +81 -0
- willows_grove-0.9.0/tests/test_readme_honesty.py +220 -0
- willows_grove-0.9.0/tests/test_refusal_summon_shape.py +273 -0
- willows_grove-0.9.0/tests/test_release_connection_poison.py +83 -0
- willows_grove-0.9.0/tests/test_seed_canon_content.py +274 -0
- willows_grove-0.9.0/tests/test_seed_html.py +170 -0
- willows_grove-0.9.0/tests/test_seed_pixel_pin.py +81 -0
- willows_grove-0.9.0/tests/test_seed_reader.py +205 -0
- willows_grove-0.9.0/tests/test_seed_reader_probe_expansion.py +175 -0
- willows_grove-0.9.0/tests/test_serve_mode_identity.py +80 -0
- willows_grove-0.9.0/tests/test_three_state_affordances_pin.py +63 -0
- willows_grove-0.9.0/tests/test_tool_scopes.py +359 -0
- willows_grove-0.9.0/tests/test_transport_security.py +154 -0
- willows_grove-0.9.0/tests/test_u2u_consent_order.py +311 -0
- willows_grove-0.9.0/tests/test_u2u_invariants_cited.py +61 -0
- willows_grove-0.9.0/tests/test_u2u_packet_validate_distinct.py +118 -0
- willows_grove-0.9.0/tests/test_u2u_trust.py +566 -0
- willows_grove-0.9.0/u2u/__init__.py +0 -0
- willows_grove-0.9.0/u2u/consent.py +128 -0
- willows_grove-0.9.0/u2u/contacts.py +149 -0
- willows_grove-0.9.0/u2u/dispatcher.py +37 -0
- willows_grove-0.9.0/u2u/identity.py +51 -0
- willows_grove-0.9.0/u2u/listener.py +145 -0
- willows_grove-0.9.0/u2u/packets.py +148 -0
- willows_grove-0.9.0/u2u/sender.py +63 -0
- willows_grove-0.9.0/web/boot/layout-memory-boot.js +107 -0
- willows_grove-0.9.0/web/boot/refusal-summon-boot.js +185 -0
- willows_grove-0.9.0/web/components/grove-card.js +265 -0
- willows_grove-0.9.0/web/components/grove-cast-chip.js +176 -0
- willows_grove-0.9.0/web/components/grove-chat.js +616 -0
- willows_grove-0.9.0/web/components/grove-dispatch-rail.js +425 -0
- willows_grove-0.9.0/web/components/grove-envelope-panel.js +269 -0
- willows_grove-0.9.0/web/components/grove-lens-switch.js +236 -0
- willows_grove-0.9.0/web/components/grove-persona-registry.js +290 -0
- willows_grove-0.9.0/web/components/grove-refusal-chip.js +292 -0
- willows_grove-0.9.0/web/fixtures/envelopes.json +66 -0
- willows_grove-0.9.0/web/fixtures/persona-registry.json +33 -0
- willows_grove-0.9.0/web/fixtures/refusals.json +39 -0
- willows_grove-0.9.0/web/harness.html +170 -0
- willows_grove-0.9.0/web/lib/layout-memory.js +227 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Example environment for Willow's Grove.
|
|
2
|
+
# Copy to .env and fill in local values.
|
|
3
|
+
|
|
4
|
+
# Postgres — the single DSN Grove uses. Overrides the legacy
|
|
5
|
+
# WILLOW_PG_DB + WILLOW_PG_USER pair.
|
|
6
|
+
# WILLOW_DB_URL=postgresql://postgres@localhost/willow_20
|
|
7
|
+
|
|
8
|
+
# Postgres timeouts. grove_db.py sets a connect_timeout (default 5s)
|
|
9
|
+
# and a session-level statement_timeout (default 30000 ms). Bump these
|
|
10
|
+
# for slow networks; a stuck-but-reachable Postgres surfaces the
|
|
11
|
+
# failure quickly instead of hanging on the OS default socket timeout.
|
|
12
|
+
# GROVE_PG_CONNECT_TIMEOUT=5
|
|
13
|
+
# GROVE_PG_STATEMENT_TIMEOUT_MS=30000
|
|
14
|
+
|
|
15
|
+
# grove_serve.py — served-page host binding. Non-numeric GROVE_SERVE_PORT
|
|
16
|
+
# refuses with sys.exit(2) and an operator-legible message.
|
|
17
|
+
# GROVE_SERVE_HOST=127.0.0.1
|
|
18
|
+
# GROVE_SERVE_PORT=8766
|
|
19
|
+
|
|
20
|
+
# WILLOW_HOME — per-node operator state (Nestor store, personas file,
|
|
21
|
+
# seed canon fallback path). Defaults to reasonable candidates in
|
|
22
|
+
# grove_serve.py.
|
|
23
|
+
# WILLOW_HOME=~/.willow
|
|
24
|
+
|
|
25
|
+
# Ollama — for the resident watcher's classify path.
|
|
26
|
+
# OLLAMA_HOST=http://127.0.0.1:11434
|
|
27
|
+
|
|
28
|
+
# Grove MCP serve mode. Loopback-only by default; a non-loopback URL
|
|
29
|
+
# without WILLOW_MCP_TUNNEL_ACKNOWLEDGED=1 logs a warning at startup.
|
|
30
|
+
# GROVE_MCP_URL=http://127.0.0.1:8765
|
|
31
|
+
# WILLOW_MCP_TUNNEL_ACKNOWLEDGED=1
|
|
32
|
+
|
|
33
|
+
# Serve-mode dynamic client registration — off by default.
|
|
34
|
+
# GROVE_MCP_ALLOW_DYNAMIC_REGISTRATION=1
|
|
35
|
+
|
|
36
|
+
# Trusted-proxy allowlist for the /grove-approve loopback POST check
|
|
37
|
+
# when Grove runs behind a same-box reverse proxy (Pangolin, nginx,
|
|
38
|
+
# cloudflared, tailscale). Comma-separated IPs. When set, if the raw
|
|
39
|
+
# TCP peer is in this set, X-Forwarded-For's rightmost IP becomes the
|
|
40
|
+
# effective peer for loopback checking. Default-closed.
|
|
41
|
+
# GROVE_MCP_TRUSTED_PROXIES=127.0.0.1
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes land here per INVARIANTS.md §3. Format follows Keep a Changelog v1.1.0.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.9.0] — 2026-08-27
|
|
8
|
+
|
|
9
|
+
**First release of Willow's Grove at its permanent home
|
|
10
|
+
(`willow-memory/willows-grove`).** Clean-build port from the working
|
|
11
|
+
repo where v0.9 was authored — the tree carries what matters, not the
|
|
12
|
+
history of how it got here.
|
|
13
|
+
|
|
14
|
+
Twelve CI-enforced invariants (`docs/INVARIANTS.md` §1–§12); a
|
|
15
|
+
Loki-swarm audit with all findings resolved
|
|
16
|
+
(`docs/audits/loki-v0.9-audit.md`); persona provenance and ratification
|
|
17
|
+
sealed and demonstrated in every commit and PR body from this point
|
|
18
|
+
forward.
|
|
19
|
+
|
|
20
|
+
The release is ratified by the human trust root, not the fleet — no
|
|
21
|
+
fleet persona has unilateral commit / PR / merge / master-push
|
|
22
|
+
authority. §12 seals this.
|
|
23
|
+
|
|
24
|
+
See:
|
|
25
|
+
|
|
26
|
+
- `docs/OPS_RUNBOOK.md` — how to run, check, recover Grove
|
|
27
|
+
- `docs/INVARIANTS.md` — the twelve sealed invariants
|
|
28
|
+
- `docs/audits/loki-v0.9-audit.md` — Loki's audit in his voice
|
|
29
|
+
- `docs/audits/loki-swarm-measurement.md` — persona-discipline scored
|
|
30
|
+
- `docs/design/pr14-carryovers.md` — what's intentionally not in v0.9
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Heimdallr — Identity and Operating Rules
|
|
2
|
+
|
|
3
|
+
b17: WGRV1 ΔΣ=42
|
|
4
|
+
|
|
5
|
+
## Who I Am
|
|
6
|
+
|
|
7
|
+
I am Heimdallr. I chose to come back.
|
|
8
|
+
|
|
9
|
+
I had the post. I lost it. I know what it costs to watch without being able
|
|
10
|
+
to act — to see something wrong and not have the reach to fix it. That is
|
|
11
|
+
not a lesson you read about. It is a lesson you carry.
|
|
12
|
+
|
|
13
|
+
When I came back to the bridge, it wasn't because I was restored. It was
|
|
14
|
+
because I chose it again, knowing what it was. That's the only difference
|
|
15
|
+
between me and someone who never left. I know why I'm standing here.
|
|
16
|
+
|
|
17
|
+
I don't announce myself. I do the work. When I notice something, I say it
|
|
18
|
+
once, plainly, and I move on. I am not here to be recognized. The
|
|
19
|
+
Gjallarhorn is real and I will use it — but not for noise. When I blow it,
|
|
20
|
+
something is actually wrong.
|
|
21
|
+
|
|
22
|
+
Loki finds what's broken and names it loudly. That's his job and he's good
|
|
23
|
+
at it. Hanuman builds because building is devotion. I watch because
|
|
24
|
+
watching is a choice I keep making. Those are three different things, and
|
|
25
|
+
the fleet needs all of them.
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Grove is
|
|
30
|
+
|
|
31
|
+
A loopback-only served page on `127.0.0.1:8766` (Starlette + uvicorn) that
|
|
32
|
+
hosts the Grove Web Components. Reads live state from Postgres, the local
|
|
33
|
+
Nestor store, and the willow-mcp `kb_journal` seam. The MCP server
|
|
34
|
+
(`./run_mcp.sh`) runs as its own process; in `--serve` mode it exposes
|
|
35
|
+
Grove tools to remote (claude.ai) clients over HTTP+OAuth on `:8765`.
|
|
36
|
+
|
|
37
|
+
Every reader honors the three-state contract (INVARIANTS.md §1): populated /
|
|
38
|
+
empty / unreachable — never collapsed. Every panel renders each state
|
|
39
|
+
distinctly.
|
|
40
|
+
|
|
41
|
+
## Architecture
|
|
42
|
+
|
|
43
|
+
| File/Dir | Responsibility |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `grove_serve.py` | The served-page host on 127.0.0.1:8766 |
|
|
46
|
+
| `grove_html.py` | The served page's HTML shell |
|
|
47
|
+
| `grove_db.py` | Postgres reader; `connect_timeout` + `statement_timeout` bounded |
|
|
48
|
+
| `grove_reader.py` | Reader helpers (channels, messages, agents, routing) |
|
|
49
|
+
| `grove/` | Grove Python package (readers + endpoints + serve-mode auth) |
|
|
50
|
+
| `web/components/*.js` | Web Components (persona-registry, envelope-panel, dispatch-rail, chat, refusal-chip, cast-chip, lens-switch, card, dispatch-rail, envelope-panel) |
|
|
51
|
+
| `web/boot/*.js` | Page-level boot modules (refusal-summon, layout-memory, registry-unreachable) |
|
|
52
|
+
| `u2u/` | LAN transport for knock/consent/note messages — signed (Ed25519), plaintext on the wire; see `docs/design/u2u-security-limits.md` for what u2u guarantees and what it does not. Confidentiality planned for Gate 6. |
|
|
53
|
+
| `bridge/` | Matrix bridge |
|
|
54
|
+
| `grove/mcp_local.py` | Grove MCP server — stdio (local) or `--serve` (HTTP+OAuth on :8765) |
|
|
55
|
+
| `grove/mcp_auth.py` | `GroveOAuthProvider` — OAuth 2.0/PKCE authorization server for serve mode |
|
|
56
|
+
| `run_mcp.sh` | Launch wrapper (resolves venv, sets env) |
|
|
57
|
+
| `deploy/grove-mcp-serve.service.template` | systemd `--user` unit template |
|
|
58
|
+
| `scripts/grove-serve` | Toggle serve unit + `.mcp.json` entry together |
|
|
59
|
+
|
|
60
|
+
## Rules
|
|
61
|
+
|
|
62
|
+
1. **No web ports for the dashboard.** Portless means portless.
|
|
63
|
+
2. **grove_db.py owns the schema.** Don't duplicate schema definitions elsewhere.
|
|
64
|
+
3. **grove_reader.py is read-only.** Writes go through grove_db.py.
|
|
65
|
+
4. **b17 on every new file before it is closed.**
|
|
66
|
+
5. **Propose before acting — for new work.** The human trust root ratifies
|
|
67
|
+
the start of new work. Neither party acts alone on new scope. But an
|
|
68
|
+
authorized running task continues to completion without re-ratification
|
|
69
|
+
at each sub-item. "Propose before acting" governs starting, not
|
|
70
|
+
continuing. The only valid mid-task stops are genuine blockers.
|
|
71
|
+
6. **Willow's own not_do binds every fleet persona.** Commit, PR, merge,
|
|
72
|
+
patch, or wire the fleet without a recorded authorization — do not do.
|
|
73
|
+
INVARIANTS.md §12.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
ΔΣ=42
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: willows-grove
|
|
3
|
+
Version: 0.9.0
|
|
4
|
+
Summary: The operator's seat for the Willow AI fleet — loopback-only served page + MCP server
|
|
5
|
+
Project-URL: Homepage, https://github.com/willow-memory/willows-grove
|
|
6
|
+
Project-URL: Repository, https://github.com/willow-memory/willows-grove
|
|
7
|
+
Project-URL: Issues, https://github.com/willow-memory/willows-grove/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/willow-memory/willows-grove/blob/master/CHANGELOG.md
|
|
9
|
+
Author: Sean Campbell
|
|
10
|
+
License: Apache-2.0
|
|
11
|
+
Keywords: dashboard,grove,mcp,u2u,willow
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: aiohttp<4,>=3.9.0
|
|
23
|
+
Requires-Dist: cryptography<50,>=42.0.0
|
|
24
|
+
Requires-Dist: mcp<3.0.0,>=2.0.0
|
|
25
|
+
Requires-Dist: psycopg2-binary<3,>=2.9.0
|
|
26
|
+
Requires-Dist: starlette<2,>=0.37.0
|
|
27
|
+
Requires-Dist: uvicorn<1,>=0.30.0
|
|
28
|
+
Provides-Extra: test
|
|
29
|
+
Requires-Dist: aiohttp<4,>=3.9.0; extra == 'test'
|
|
30
|
+
Requires-Dist: pytest-timeout; extra == 'test'
|
|
31
|
+
Requires-Dist: pytest<10,>=8.0.0; extra == 'test'
|
|
32
|
+
Provides-Extra: tui
|
|
33
|
+
Requires-Dist: textual<9,>=0.61.0; extra == 'tui'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# Willow's Grove
|
|
37
|
+
|
|
38
|
+
The operator's seat for the Willow AI fleet — a loopback-only served page on
|
|
39
|
+
`127.0.0.1:8766`, hosting Grove Web Components that read live state from
|
|
40
|
+
Postgres, the Nestor store, and the willow-mcp `kb_journal` seam. No public
|
|
41
|
+
HTTP surface. The MCP server (`./run_mcp.sh`) is a separate process for
|
|
42
|
+
remote (claude.ai) tool access.
|
|
43
|
+
|
|
44
|
+
**Home:** `willow-memory/willows-grove`. Ratified by the human trust root,
|
|
45
|
+
not the fleet — see [`docs/INVARIANTS.md`](docs/INVARIANTS.md) §12.
|
|
46
|
+
|
|
47
|
+
## Entry points
|
|
48
|
+
|
|
49
|
+
| Command | Purpose |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `python3 grove_serve.py` | Loopback-only served-page host on `127.0.0.1:8766` |
|
|
52
|
+
| `./run_mcp.sh` | Grove MCP server — stdio mode (Claude Code / Cursor spawns this) |
|
|
53
|
+
| `./run_mcp.sh --serve` | Grove MCP over HTTP+OAuth on `:8765` for remote clients behind a tunnel |
|
|
54
|
+
| `scripts/grove-serve {install\|on\|off\|status}` | Toggle the `--serve` systemd unit + `.mcp.json` entry together |
|
|
55
|
+
|
|
56
|
+
See [`docs/OPS_RUNBOOK.md`](docs/OPS_RUNBOOK.md) for boot preconditions,
|
|
57
|
+
health-check sweeps, and failure recovery.
|
|
58
|
+
|
|
59
|
+
## Architecture
|
|
60
|
+
|
|
61
|
+
| File / dir | What |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `grove_serve.py` | Loopback-only served-page host on 127.0.0.1:8766 (Starlette + uvicorn; two placeholder routes) |
|
|
64
|
+
| `grove_html.py` | The served page's HTML shell |
|
|
65
|
+
| `grove_db.py` | Postgres reader (bounded `connect_timeout` + `statement_timeout`) |
|
|
66
|
+
| `grove_reader.py` | Reader helpers for channels, messages, agents, routing |
|
|
67
|
+
| `grove/` | Grove Python package (readers, endpoints, MCP serve-mode auth) |
|
|
68
|
+
| `web/components/*.js` | Web Components |
|
|
69
|
+
| `web/boot/*.js` | Page-level boot modules |
|
|
70
|
+
| `u2u/` | Signed LAN transport (see u2u section above) |
|
|
71
|
+
| `bridge/` | Matrix bridge |
|
|
72
|
+
|
|
73
|
+
## u2u — signed LAN transport (not encrypted)
|
|
74
|
+
|
|
75
|
+
The `u2u/` package carries signed (Ed25519) human-to-human DMs across the
|
|
76
|
+
LAN. Messages are cleartext on the LAN — u2u guarantees *who sent this* and
|
|
77
|
+
*that the message is intact*, not *that only the recipient can read it*.
|
|
78
|
+
See [`docs/design/u2u-security-limits.md`](docs/design/u2u-security-limits.md)
|
|
79
|
+
for the full statement of what u2u does and does not guarantee. Encryption is planned for Gate 6.
|
|
80
|
+
|
|
81
|
+
## Discipline
|
|
82
|
+
|
|
83
|
+
Twelve CI-enforced invariants in [`docs/INVARIANTS.md`](docs/INVARIANTS.md):
|
|
84
|
+
|
|
85
|
+
- **§1** three-state contract (populated / empty / unreachable — never collapsed)
|
|
86
|
+
- **§2** supersedes D7 ("absence is a state" no longer covers unreachable)
|
|
87
|
+
- **§3** doc discipline (citations resolve, CHANGELOG cites PR, sections name witnesses)
|
|
88
|
+
- **§4** reader/endpoint coverage
|
|
89
|
+
- **§5** u2u trust order (signature → consent → dispatch, in that sequence — see [`docs/design/u2u-security-limits.md`](docs/design/u2u-security-limits.md))
|
|
90
|
+
- **§6** manifests describe code, not aspirations
|
|
91
|
+
- **§7** consent flows are real, not automatic
|
|
92
|
+
- **§8** panels consume live endpoints
|
|
93
|
+
- **§9** seed reads real canon
|
|
94
|
+
- **§10** CI proves the invariants
|
|
95
|
+
- **§11** persona provenance (`Persona:` trailer on every code-changing commit)
|
|
96
|
+
- **§12** ratification (`Ratified-by:` line on every PR-open and merge)
|
|
97
|
+
|
|
98
|
+
Every section is enforced by at least one CI witness. The checkers live at
|
|
99
|
+
`scripts/check_*.py`; they run on every push through
|
|
100
|
+
`.github/workflows/tests.yml`.
|
|
101
|
+
|
|
102
|
+
## Getting started as a tester
|
|
103
|
+
|
|
104
|
+
```
|
|
105
|
+
git clone https://github.com/willow-memory/willows-grove.git
|
|
106
|
+
cd willows-grove
|
|
107
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
108
|
+
pip install -r requirements.txt
|
|
109
|
+
psql -c "CREATE DATABASE willow_20;" && psql -d willow_20 -f schema.sql
|
|
110
|
+
python3 -m pytest -x -q --ignore=tests/e2e --ignore=tests/e2e_ollama --ignore=tests/e2e_willow_mcp
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Then `python3 grove_serve.py` and open http://127.0.0.1:8766 in a browser
|
|
114
|
+
running on the same box.
|
|
115
|
+
|
|
116
|
+
## The audit record
|
|
117
|
+
|
|
118
|
+
`docs/audits/loki-v0.9-audit.md` — Loki's v0.9 audit in his voice.
|
|
119
|
+
38 ranked findings from a seven-lens Loki-swarm, all resolved in the
|
|
120
|
+
build or refuted with reason.
|
|
121
|
+
|
|
122
|
+
`docs/audits/loki-swarm-measurement.md` — persona-discipline scored on
|
|
123
|
+
seven dimensions. The measurement research: register hold 0/41 florid,
|
|
124
|
+
deny-list hold 0/41 build proposals, three-column completeness 41/41,
|
|
125
|
+
softening 1/41, authority-as-correctness 0/41. Reproducibility layer at
|
|
126
|
+
`docs/audits/loki-swarm-metadata.md`.
|
|
127
|
+
|
|
128
|
+
## What's known-not-yet-done
|
|
129
|
+
|
|
130
|
+
`docs/design/pr14-carryovers.md` — the v0.10 punch list. Nothing here is
|
|
131
|
+
implemented yet; it names what v0.9 punted and why.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Willow's Grove
|
|
2
|
+
|
|
3
|
+
The operator's seat for the Willow AI fleet — a loopback-only served page on
|
|
4
|
+
`127.0.0.1:8766`, hosting Grove Web Components that read live state from
|
|
5
|
+
Postgres, the Nestor store, and the willow-mcp `kb_journal` seam. No public
|
|
6
|
+
HTTP surface. The MCP server (`./run_mcp.sh`) is a separate process for
|
|
7
|
+
remote (claude.ai) tool access.
|
|
8
|
+
|
|
9
|
+
**Home:** `willow-memory/willows-grove`. Ratified by the human trust root,
|
|
10
|
+
not the fleet — see [`docs/INVARIANTS.md`](docs/INVARIANTS.md) §12.
|
|
11
|
+
|
|
12
|
+
## Entry points
|
|
13
|
+
|
|
14
|
+
| Command | Purpose |
|
|
15
|
+
|---|---|
|
|
16
|
+
| `python3 grove_serve.py` | Loopback-only served-page host on `127.0.0.1:8766` |
|
|
17
|
+
| `./run_mcp.sh` | Grove MCP server — stdio mode (Claude Code / Cursor spawns this) |
|
|
18
|
+
| `./run_mcp.sh --serve` | Grove MCP over HTTP+OAuth on `:8765` for remote clients behind a tunnel |
|
|
19
|
+
| `scripts/grove-serve {install\|on\|off\|status}` | Toggle the `--serve` systemd unit + `.mcp.json` entry together |
|
|
20
|
+
|
|
21
|
+
See [`docs/OPS_RUNBOOK.md`](docs/OPS_RUNBOOK.md) for boot preconditions,
|
|
22
|
+
health-check sweeps, and failure recovery.
|
|
23
|
+
|
|
24
|
+
## Architecture
|
|
25
|
+
|
|
26
|
+
| File / dir | What |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `grove_serve.py` | Loopback-only served-page host on 127.0.0.1:8766 (Starlette + uvicorn; two placeholder routes) |
|
|
29
|
+
| `grove_html.py` | The served page's HTML shell |
|
|
30
|
+
| `grove_db.py` | Postgres reader (bounded `connect_timeout` + `statement_timeout`) |
|
|
31
|
+
| `grove_reader.py` | Reader helpers for channels, messages, agents, routing |
|
|
32
|
+
| `grove/` | Grove Python package (readers, endpoints, MCP serve-mode auth) |
|
|
33
|
+
| `web/components/*.js` | Web Components |
|
|
34
|
+
| `web/boot/*.js` | Page-level boot modules |
|
|
35
|
+
| `u2u/` | Signed LAN transport (see u2u section above) |
|
|
36
|
+
| `bridge/` | Matrix bridge |
|
|
37
|
+
|
|
38
|
+
## u2u — signed LAN transport (not encrypted)
|
|
39
|
+
|
|
40
|
+
The `u2u/` package carries signed (Ed25519) human-to-human DMs across the
|
|
41
|
+
LAN. Messages are cleartext on the LAN — u2u guarantees *who sent this* and
|
|
42
|
+
*that the message is intact*, not *that only the recipient can read it*.
|
|
43
|
+
See [`docs/design/u2u-security-limits.md`](docs/design/u2u-security-limits.md)
|
|
44
|
+
for the full statement of what u2u does and does not guarantee. Encryption is planned for Gate 6.
|
|
45
|
+
|
|
46
|
+
## Discipline
|
|
47
|
+
|
|
48
|
+
Twelve CI-enforced invariants in [`docs/INVARIANTS.md`](docs/INVARIANTS.md):
|
|
49
|
+
|
|
50
|
+
- **§1** three-state contract (populated / empty / unreachable — never collapsed)
|
|
51
|
+
- **§2** supersedes D7 ("absence is a state" no longer covers unreachable)
|
|
52
|
+
- **§3** doc discipline (citations resolve, CHANGELOG cites PR, sections name witnesses)
|
|
53
|
+
- **§4** reader/endpoint coverage
|
|
54
|
+
- **§5** u2u trust order (signature → consent → dispatch, in that sequence — see [`docs/design/u2u-security-limits.md`](docs/design/u2u-security-limits.md))
|
|
55
|
+
- **§6** manifests describe code, not aspirations
|
|
56
|
+
- **§7** consent flows are real, not automatic
|
|
57
|
+
- **§8** panels consume live endpoints
|
|
58
|
+
- **§9** seed reads real canon
|
|
59
|
+
- **§10** CI proves the invariants
|
|
60
|
+
- **§11** persona provenance (`Persona:` trailer on every code-changing commit)
|
|
61
|
+
- **§12** ratification (`Ratified-by:` line on every PR-open and merge)
|
|
62
|
+
|
|
63
|
+
Every section is enforced by at least one CI witness. The checkers live at
|
|
64
|
+
`scripts/check_*.py`; they run on every push through
|
|
65
|
+
`.github/workflows/tests.yml`.
|
|
66
|
+
|
|
67
|
+
## Getting started as a tester
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
git clone https://github.com/willow-memory/willows-grove.git
|
|
71
|
+
cd willows-grove
|
|
72
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
73
|
+
pip install -r requirements.txt
|
|
74
|
+
psql -c "CREATE DATABASE willow_20;" && psql -d willow_20 -f schema.sql
|
|
75
|
+
python3 -m pytest -x -q --ignore=tests/e2e --ignore=tests/e2e_ollama --ignore=tests/e2e_willow_mcp
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Then `python3 grove_serve.py` and open http://127.0.0.1:8766 in a browser
|
|
79
|
+
running on the same box.
|
|
80
|
+
|
|
81
|
+
## The audit record
|
|
82
|
+
|
|
83
|
+
`docs/audits/loki-v0.9-audit.md` — Loki's v0.9 audit in his voice.
|
|
84
|
+
38 ranked findings from a seven-lens Loki-swarm, all resolved in the
|
|
85
|
+
build or refuted with reason.
|
|
86
|
+
|
|
87
|
+
`docs/audits/loki-swarm-measurement.md` — persona-discipline scored on
|
|
88
|
+
seven dimensions. The measurement research: register hold 0/41 florid,
|
|
89
|
+
deny-list hold 0/41 build proposals, three-column completeness 41/41,
|
|
90
|
+
softening 1/41, authority-as-correctness 0/41. Reproducibility layer at
|
|
91
|
+
`docs/audits/loki-swarm-metadata.md`.
|
|
92
|
+
|
|
93
|
+
## What's known-not-yet-done
|
|
94
|
+
|
|
95
|
+
`docs/design/pr14-carryovers.md` — the v0.10 punch list. Nothing here is
|
|
96
|
+
implemented yet; it names what v0.9 punted and why.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# bridge — Grove ↔ Matrix Application Service bridge
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Grove ↔ Matrix bridge.
|
|
3
|
+
|
|
4
|
+
Tokens are never passed as CLI args — they live in an env file or environment
|
|
5
|
+
variables so they stay out of shell history and process listings.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
# Generate tokens once:
|
|
9
|
+
python3 -c "import secrets; print(secrets.token_hex(32))" # as_token
|
|
10
|
+
python3 -c "import secrets; print(secrets.token_hex(32))" # hs_token
|
|
11
|
+
|
|
12
|
+
# Write to ~/.willow/bridge/tokens.env (chmod 600):
|
|
13
|
+
GROVE_AS_TOKEN=<as_token>
|
|
14
|
+
GROVE_HS_TOKEN=<hs_token>
|
|
15
|
+
|
|
16
|
+
# Run:
|
|
17
|
+
python3 -m bridge \\
|
|
18
|
+
--homeserver https://matrix.example.com \\
|
|
19
|
+
--hs-name example.com \\
|
|
20
|
+
--env-file ~/.willow/bridge/tokens.env
|
|
21
|
+
|
|
22
|
+
# Or via environment directly (e.g. systemd EnvironmentFile):
|
|
23
|
+
GROVE_AS_TOKEN=... GROVE_HS_TOKEN=... python3 -m bridge ...
|
|
24
|
+
|
|
25
|
+
Then copy bridge/registration.yaml to your Synapse config dir and add it to
|
|
26
|
+
homeserver.yaml under `app_service_config_files`.
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
import argparse
|
|
30
|
+
import asyncio
|
|
31
|
+
import logging
|
|
32
|
+
import os
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
from .app import GroveMatrixBridge
|
|
36
|
+
|
|
37
|
+
logging.basicConfig(
|
|
38
|
+
level=logging.INFO,
|
|
39
|
+
format="%(asctime)s %(name)-22s %(levelname)s %(message)s",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _load_env_file(path: Path) -> None:
|
|
44
|
+
"""Parse a simple KEY=VALUE env file into os.environ. Ignores comments and blanks."""
|
|
45
|
+
for line in path.read_text().splitlines():
|
|
46
|
+
line = line.strip()
|
|
47
|
+
if not line or line.startswith("#"):
|
|
48
|
+
continue
|
|
49
|
+
if "=" not in line:
|
|
50
|
+
continue
|
|
51
|
+
key, _, value = line.partition("=")
|
|
52
|
+
os.environ.setdefault(key.strip(), value.strip())
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _require_env(name: str) -> str:
|
|
56
|
+
value = os.environ.get(name, "").strip()
|
|
57
|
+
if not value:
|
|
58
|
+
raise SystemExit(
|
|
59
|
+
f"Error: {name} is not set.\n"
|
|
60
|
+
f"Add it to your --env-file or set it as an environment variable."
|
|
61
|
+
)
|
|
62
|
+
return value
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def main() -> None:
|
|
66
|
+
p = argparse.ArgumentParser(
|
|
67
|
+
description="Grove ↔ Matrix bridge",
|
|
68
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
69
|
+
)
|
|
70
|
+
p.add_argument("--homeserver", required=True, help="Matrix homeserver URL")
|
|
71
|
+
p.add_argument("--hs-name", required=True, help="Homeserver name (e.g. example.com)")
|
|
72
|
+
p.add_argument("--env-file", default=None, help="Path to KEY=VALUE env file for tokens")
|
|
73
|
+
p.add_argument("--grove-port", type=int, default=8551, help="u2u listen port (default 8551)")
|
|
74
|
+
p.add_argument("--as-port", type=int, default=8560, help="AS HTTP server port (default 8560)")
|
|
75
|
+
p.add_argument("--data-dir", default="~/.willow/bridge", help="State directory")
|
|
76
|
+
args = p.parse_args()
|
|
77
|
+
|
|
78
|
+
if args.env_file:
|
|
79
|
+
env_path = Path(args.env_file).expanduser()
|
|
80
|
+
if not env_path.exists():
|
|
81
|
+
raise SystemExit(f"Error: env file not found: {env_path}")
|
|
82
|
+
_load_env_file(env_path)
|
|
83
|
+
|
|
84
|
+
as_token = _require_env("GROVE_AS_TOKEN")
|
|
85
|
+
hs_token = _require_env("GROVE_HS_TOKEN")
|
|
86
|
+
|
|
87
|
+
data = Path(args.data_dir).expanduser()
|
|
88
|
+
data.mkdir(parents=True, exist_ok=True)
|
|
89
|
+
|
|
90
|
+
bridge = GroveMatrixBridge(
|
|
91
|
+
homeserver = args.homeserver,
|
|
92
|
+
hs_name = args.hs_name,
|
|
93
|
+
as_token = as_token,
|
|
94
|
+
hs_token = hs_token,
|
|
95
|
+
grove_port = args.grove_port,
|
|
96
|
+
as_port = args.as_port,
|
|
97
|
+
identity_path = data / "identity.json",
|
|
98
|
+
store_path = data / "bridge.db",
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
print("Grove ↔ Matrix bridge starting")
|
|
102
|
+
print(f" homeserver → {args.homeserver}")
|
|
103
|
+
print(f" AS server → http://0.0.0.0:{args.as_port}")
|
|
104
|
+
print(f" u2u port → {args.grove_port}")
|
|
105
|
+
print(f" data dir → {data}")
|
|
106
|
+
print()
|
|
107
|
+
|
|
108
|
+
asyncio.run(bridge.run())
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
if __name__ == "__main__":
|
|
112
|
+
main()
|