yothere 1.5.2__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 (93) hide show
  1. yothere-1.5.2/.gitignore +14 -0
  2. yothere-1.5.2/CHANGELOG.md +227 -0
  3. yothere-1.5.2/CONTRIBUTING.md +76 -0
  4. yothere-1.5.2/LICENSE +21 -0
  5. yothere-1.5.2/PKG-INFO +201 -0
  6. yothere-1.5.2/QUICKSTART.md +80 -0
  7. yothere-1.5.2/README.md +155 -0
  8. yothere-1.5.2/SECURITY.md +49 -0
  9. yothere-1.5.2/pyproject.toml +92 -0
  10. yothere-1.5.2/src/yothere/__init__.py +10 -0
  11. yothere-1.5.2/src/yothere/attention.py +236 -0
  12. yothere-1.5.2/src/yothere/board.py +220 -0
  13. yothere-1.5.2/src/yothere/brain/__init__.py +16 -0
  14. yothere-1.5.2/src/yothere/brain/registry.py +196 -0
  15. yothere-1.5.2/src/yothere/brain_advance.py +336 -0
  16. yothere-1.5.2/src/yothere/capture.py +158 -0
  17. yothere-1.5.2/src/yothere/card.py +297 -0
  18. yothere-1.5.2/src/yothere/cli.py +624 -0
  19. yothere-1.5.2/src/yothere/config.py +268 -0
  20. yothere-1.5.2/src/yothere/demo_brain.py +167 -0
  21. yothere-1.5.2/src/yothere/doctor.py +342 -0
  22. yothere-1.5.2/src/yothere/envcompat.py +59 -0
  23. yothere-1.5.2/src/yothere/experiments/fixtures.jsonl +18 -0
  24. yothere-1.5.2/src/yothere/fleet_sources/__init__.py +7 -0
  25. yothere-1.5.2/src/yothere/fleet_sources/remote.py +86 -0
  26. yothere-1.5.2/src/yothere/fleet_state.py +263 -0
  27. yothere-1.5.2/src/yothere/listen.py +390 -0
  28. yothere-1.5.2/src/yothere/llm.py +62 -0
  29. yothere-1.5.2/src/yothere/maintenance.py +190 -0
  30. yothere-1.5.2/src/yothere/mcp_register.py +200 -0
  31. yothere-1.5.2/src/yothere/mcp_server.py +121 -0
  32. yothere-1.5.2/src/yothere/notify.py +47 -0
  33. yothere-1.5.2/src/yothere/onboarding.py +501 -0
  34. yothere-1.5.2/src/yothere/presence.py +157 -0
  35. yothere-1.5.2/src/yothere/presets.py +161 -0
  36. yothere-1.5.2/src/yothere/pwa.py +295 -0
  37. yothere-1.5.2/src/yothere/ratelimit.py +143 -0
  38. yothere-1.5.2/src/yothere/render.py +518 -0
  39. yothere-1.5.2/src/yothere/runner.py +659 -0
  40. yothere-1.5.2/src/yothere/runner_watchdog.py +157 -0
  41. yothere-1.5.2/src/yothere/scope.py +293 -0
  42. yothere-1.5.2/src/yothere/service.py +429 -0
  43. yothere-1.5.2/src/yothere/spawn.py +123 -0
  44. yothere-1.5.2/src/yothere/store.py +248 -0
  45. yothere-1.5.2/src/yothere/task_source.py +721 -0
  46. yothere-1.5.2/src/yothere/tenant_supervisor.py +189 -0
  47. yothere-1.5.2/src/yothere/thread_model/__init__.py +30 -0
  48. yothere-1.5.2/src/yothere/thread_model/ask.py +171 -0
  49. yothere-1.5.2/src/yothere/thread_model/autonomy.py +171 -0
  50. yothere-1.5.2/src/yothere/thread_model/prompts/thread-system.md +174 -0
  51. yothere-1.5.2/src/yothere/thread_model/resume.py +542 -0
  52. yothere-1.5.2/src/yothere/thread_model/thread.py +357 -0
  53. yothere-1.5.2/src/yothere/thread_model/vt_config.py +182 -0
  54. yothere-1.5.2/src/yothere/thread_model/worker_policy.py +97 -0
  55. yothere-1.5.2/src/yothere/voice.py +418 -0
  56. yothere-1.5.2/src/yothere/voicecall/__init__.py +29 -0
  57. yothere-1.5.2/src/yothere/voicecall/auth.py +547 -0
  58. yothere-1.5.2/src/yothere/voicecall/bob_brain.py +392 -0
  59. yothere-1.5.2/src/yothere/voicecall/config.py +335 -0
  60. yothere-1.5.2/src/yothere/voicecall/dispatch.py +58 -0
  61. yothere-1.5.2/src/yothere/voicecall/echo_brain.py +120 -0
  62. yothere-1.5.2/src/yothere/voicecall/headless_call.py +823 -0
  63. yothere-1.5.2/src/yothere/voicecall/overview_state.py +428 -0
  64. yothere-1.5.2/src/yothere/voicecall/pipeline.py +720 -0
  65. yothere-1.5.2/src/yothere/voicecall/remote_brain.py +399 -0
  66. yothere-1.5.2/src/yothere/voicecall/requirements.lock +127 -0
  67. yothere-1.5.2/src/yothere/voicecall/requirements.txt +19 -0
  68. yothere-1.5.2/src/yothere/voicecall/session_manager.py +1808 -0
  69. yothere-1.5.2/src/yothere/voicecall/ubob_brain.py +333 -0
  70. yothere-1.5.2/src/yothere/voicecall/webrtc_server.py +2174 -0
  71. yothere-1.5.2/src/yothere/web.py +42 -0
  72. yothere-1.5.2/src/yothere/worker.py +156 -0
  73. yothere-1.5.2/src/yothere/worker_hooks/__init__.py +72 -0
  74. yothere-1.5.2/src/yothere/worker_hooks/send_deny_gate.py +311 -0
  75. yothere-1.5.2/tests/relay_test.py +2508 -0
  76. yothere-1.5.2/tests/test_auth_pg.py +142 -0
  77. yothere-1.5.2/tests/test_call_visibility.py +512 -0
  78. yothere-1.5.2/tests/test_doctor.py +272 -0
  79. yothere-1.5.2/tests/test_example_brain.py +102 -0
  80. yothere-1.5.2/tests/test_init.py +203 -0
  81. yothere-1.5.2/tests/test_invites.py +179 -0
  82. yothere-1.5.2/tests/test_mcp_register.py +194 -0
  83. yothere-1.5.2/tests/test_offmode_parity.py +87 -0
  84. yothere-1.5.2/tests/test_onboarding.py +334 -0
  85. yothere-1.5.2/tests/test_registry.py +217 -0
  86. yothere-1.5.2/tests/test_render.py +298 -0
  87. yothere-1.5.2/tests/test_service.py +250 -0
  88. yothere-1.5.2/tests/test_session_status.py +122 -0
  89. yothere-1.5.2/tests/test_task_source.py +498 -0
  90. yothere-1.5.2/tests/test_tenant_supervisor.py +142 -0
  91. yothere-1.5.2/tests/test_user_scoping.py +140 -0
  92. yothere-1.5.2/tests/test_worker_policy.py +449 -0
  93. yothere-1.5.2/tests/voicecall_test.py +2862 -0
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ .ruff_cache/
5
+ *.egg-info/
6
+ build/
7
+ dist/
8
+ # runtime state (never committed)
9
+ threads/
10
+ threads-archive/
11
+ *.log
12
+ relay-board.html
13
+ ping-state.json
14
+ runner.lock
@@ -0,0 +1,227 @@
1
+ # Changelog
2
+
3
+ All notable changes to yothere (formerly `relay-cockpit`). Format loosely follows
4
+ Keep a Changelog; versions follow semver.
5
+
6
+ ## [Unreleased]
7
+
8
+ ### Changed
9
+ - **RENAME: the package, module, and PyPI distribution are now `yothere`**
10
+ (formerly `relay-cockpit`). Concretely: the import path `relay.*` -> `yothere.*`
11
+ (`src/relay/` -> `src/yothere/`); the PyPI dist name `relay-cockpit` -> `yothere`
12
+ (`pip install yothere`, extras `[voice]`/`[web]`/`[mcp]`/`[llm]`/`[postgres]`
13
+ unchanged); the MCP console script `relay-mcp` -> `yothere-mcp` and the MCP
14
+ server registration name `relay` -> `yothere`.
15
+ - **CLI verb: `yothere` is now the canonical command (`yothere <verb>`); `relay`
16
+ is kept as a deprecated back-compat alias.** Both console scripts invoke the
17
+ same CLI, so `yothere init`/`yothere board`/... and the old `relay ...` forms
18
+ both work; help/usage and the printed next-step hints now say `yothere`.
19
+ - **Default home: `~/.yothere`** (was `~/.relay`) when neither `YOTHERE_HOME` nor
20
+ `RELAY_HOME` is set. For back-compat, an existing `~/.relay` is used as a
21
+ deprecated fallback when `~/.yothere` does not yet exist, so a host running the
22
+ old default is not stranded. Any explicit `YOTHERE_HOME`/`RELAY_HOME` still
23
+ overrides. The `relay.env` file and the `com.*.relay-*` launchd/systemd unit
24
+ names are unchanged — a running host that pins `RELAY_HOME` keeps its wiring.
25
+ - **Env vars: `YOTHERE_*` is the going-forward namespace; every legacy `RELAY_*`
26
+ name still works.** A back-compat shim (`yothere.envcompat`) mirrors the two
27
+ prefixes bidirectionally at package import (over `os.environ`) and inside the
28
+ env-file parsers, so a host still exporting `RELAY_HOME`, `RELAY_NOTIFIER`, etc.
29
+ is unaffected. The legacy `RELAY_*` names are supported for at least this
30
+ release; new configs should prefer `YOTHERE_*`.
31
+ - **SECURITY-RELEVANT DEFAULT: `claude`-harness worker turns now run
32
+ `--permission-mode bypassPermissions` by default** (full capability), contained
33
+ by a relay-owned PreToolUse send-gate injected via `--settings` (denies
34
+ outward sends: send scripts, `git push`, HTTP/mail egress with a body, MCP
35
+ send tools; plain GET reads stay allowed). Self-hosters upgrading get this
36
+ posture change; opt out with `worker_permission_mode: default` in vt config or
37
+ `RELAY_WORKER_PERMISSION_MODE=default`. The gate fails CLOSED: if its settings
38
+ file cannot be written, the turn drops `--permission-mode` entirely. Hosted
39
+ tenants are clamped to `default` unless the operator sets
40
+ `RELAY_HOSTED_FULL_CAPABILITY=1`. The gate is best-effort argv inspection
41
+ (defense-in-depth), not an adversarial sandbox.
42
+
43
+ Deferred, explicitly out of v1:
44
+ - Brain-side token lifecycle: issue / rotate / revoke / scope.
45
+ - Publish to PyPI (wheels currently ship via GitHub Releases).
46
+
47
+ ## [1.5.1] — 2026-07-01
48
+
49
+ Version-integrity release. No runtime code changes.
50
+
51
+ ### Fixed
52
+ - **Wheel version metadata** — the v1.5.0 tag was cut without bumping
53
+ `pyproject.toml`, so the published "v1.5.0" wheel self-reports 1.4.0. This
54
+ release restores tag == package version (1.5.0 is skipped as ambiguous).
55
+
56
+ ### Added
57
+ - **`relay init` — zero-key first run** (`relay.cli`, new `relay.demo_brain`):
58
+ one idempotent command that creates the `RELAY_HOME` layout, detects any
59
+ existing brain (env url / `brains.yaml` / `claude` CLI), seeds a commented
60
+ `brains.yaml` with a bundled demo brain when the file doesn't exist (never
61
+ overwrites anything), and prints the exact next commands. `relay init --demo`
62
+ starts the bundled Brain Protocol v1 demo brain on a free localhost port and
63
+ drives one canned thread through the real runner -> worker -> brain path
64
+ (blocked -> reply -> done), filling the board with no API key.
65
+ - Remote-brain `done` turns now also write `summary` (not just `last_result`),
66
+ so a remote brain's final answer actually renders on the board / `relay show`.
67
+ - **Release guard** in `.github/workflows/release.yml`: the job fails early when
68
+ the pushed tag does not match the `pyproject.toml` version, so a metadata
69
+ mismatch can never publish again.
70
+ - `docs/RELEASING.md` — the release checklist (bump + changelog + tag + verify).
71
+ - Tester onboarding guide (`docs/ONBOARDING.md`) + tester-feedback issue
72
+ template (#12; merged after the v1.5.0 tag, so first shipped in this wheel).
73
+
74
+ ## [1.5.0] — 2026-06-30
75
+
76
+ Customer-cockpit Session 4b: the locally-proven self-onboarding cockpit becomes a
77
+ deployable single-box hosted service (Fly.io), with tenant agents advancing
78
+ against a hosted brain over the Brain Protocol. (#11)
79
+
80
+ > Note: the published v1.5.0 wheel self-reports version 1.4.0 (the tag was cut
81
+ > without a `pyproject.toml` bump) — fixed in 1.5.1.
82
+
83
+ ### Added
84
+ - **Postgres-backed AuthStore** (`[postgres]` extra) — a one-file driver seam in
85
+ `relay.voicecall.auth`: `DATABASE_URL` set → Postgres via lazily-imported
86
+ psycopg; unset → SQLite, byte-identical to before. Schema self-initialises on
87
+ first connect; public method surface unchanged.
88
+ - **Per-tenant hosted brain** — accounts gain nullable `brain_url`/`brain_token`
89
+ + `auth.tenant_brain()` (per-account override, else deploy-wide
90
+ `RELAY_HOSTED_BRAIN_URL`/`TOKEN`); the tenant supervisor injects
91
+ `RELAY_REMOTE_BRAIN_URL`/`TOKEN` per tenant so each runner's `remote` harness
92
+ advances with that tenant's token.
93
+ - **`relay.web` + `config.web_config()`** — boot the FastAPI cockpit without the
94
+ Twilio/Gemini required-keys gate (voice degrades to "unavailable"); lean
95
+ `[web]` extra (FastAPI/uvicorn only, no media stack).
96
+ - **Container + deploy**: `deploy/{Dockerfile,entrypoint.sh,fly.toml}`
97
+ (python:3.12-slim, tini, one always-on machine, durable `/data` volume) +
98
+ `docs/DEPLOY-fly.md` runbook.
99
+ - CI: dedicated Postgres parity job (`tests/test_auth_pg.py`) + off-mode parity
100
+ suite (`tests/test_offmode_parity.py`) locking the local-first invariant.
101
+
102
+ ## [1.4.0] — 2026-06-30
103
+
104
+ ### Added
105
+ - **Opt-in keyless tailnet Connect (`RELAY_TRUST_TAILNET_IDENTITY=1`)** — the
106
+ bearer-gated cockpit routes accept the verified `tailscale-user-login` header
107
+ injected by `tailscale serve`, so a tailnet browser connects without the token
108
+ in the URL. Spoof-safe: the identity is trusted only when no cloudflare edge
109
+ marker (`cf-ray`/`cf-connecting-ip`) is present, so the public cloudflared
110
+ tunnel can never forge it. Default (flag unset) is byte-identical:
111
+ fully bearer-gated. (#10)
112
+
113
+ ## [1.3.0] — 2026-06-30
114
+
115
+ Customer-cockpit Session 4: self-onboarding, local-first. A stranger redeems an
116
+ invite, signs up, and is walked through a guided first task — zero terminal
117
+ steps. All surfaces are hosted-mode-only; off mode stays byte-identical.
118
+
119
+ ### Added
120
+ - **Self-onboarding (hosted mode)** — invite-gated `GET/POST /signup` +
121
+ guided `GET/POST /onboarding` (same spawn front door as `/dispatch`); invites
122
+ table with atomic single-use consume, per-account `budget_usd`, and a
123
+ `relay invite create` CLI. Both routes 404 in off mode. (#8)
124
+ - **Runner-per-tenant supervisor** (`relay.tenant_supervisor`) — reconciles one
125
+ `relay.runner loop` per tenant with per-tenant `RELAY_HOME`/`RELAY_USER` and a
126
+ daily cost cap (`RELAY_DAILY_COST_CAP_USD`), so non-Phil tenants' threads
127
+ actually advance. (#8)
128
+ - **MCP server surface** (`relay.mcp_server`, `relay-mcp` console script, `[mcp]`
129
+ extra) — drive a fleet from any MCP client over stdio: `spawn_thread`, `board`,
130
+ `reply`. Wraps the same spawn/store/reply front doors as the CLI/cockpit; the tool
131
+ logic is SDK-free `_*_impl` helpers so it imports + tests without `mcp` installed.
132
+ Single-user/local (drives `~/.relay`); orthogonal to the hosted cockpit. (#7)
133
+
134
+ ## [1.2.0] — 2026-06-30
135
+
136
+ Customer-cockpit Session 3: real auth + multi-tenant isolation, gated entirely
137
+ behind a deployment-mode flag so local single-user use is byte-identical to before.
138
+
139
+ ### Added
140
+ - **Hosted mode (`RELAY_AUTH_MODE=hosted`)** — a Python-native, stdlib-only auth
141
+ layer (`relay.voicecall.auth`): scrypt password hashing, opaque session token in
142
+ SQLite (`RELAY_AUTH_DB`, WAL + busy_timeout, separate from fleet state), login
143
+ lockout, KDF-on-unknown-user (no existence timing leak), token rotation on login,
144
+ and a `Secure` cookie tied to the mode (not the request URL).
145
+ - **Per-tenant isolation** — each account is scoped to its own `~/.relay-<tenant>`
146
+ home under `RELAY_TENANTS_ROOT`. The cockpit resolves the tenant per request and
147
+ threads an explicit `root=` into spawn/store/fleet_state — never the process
148
+ singleton — so two logins see two isolated fleets with no cross-tenant read.
149
+ - `GET/POST /login` + `POST /logout` routes and a brand-consistent login page; the
150
+ workspace chip now renders the logged-in account (dynamic; off mode = static).
151
+ - `fleet_state.load_fleet(root=…)`, `config.auth_mode`, `config.tenants_root`,
152
+ `config.auth_db`.
153
+
154
+ ### Changed
155
+ - In hosted mode the tailnet/loopback public carve-out is removed: every route
156
+ except `/healthz` + `/login` requires a session. Off mode is unchanged (the gate
157
+ short-circuits to the original path; no auth/tenant code runs).
158
+
159
+ ## [1.1.0] — 2026-06-30
160
+
161
+ Architecture-audit pass: re-applied the 84-finding relay audit (originally landed
162
+ in the bob monorepo) against the package layout. Net ~1250 lines removed, runner
163
+ reliability hardened, voicecall teardown made deterministic, test suite raised to
164
+ 231 (relay) + 177/3-skip (voicecall).
165
+
166
+ ### Removed
167
+ - **The dead offline router + feedback-learning loop** — `router.py`,
168
+ `router_learn.py`, and the `relay feedback` CLI subcommand are deleted (the
169
+ cascade that called `router.decide()` was retired conceptually but never removed;
170
+ it never ran in production). The live spawn-path classifier is `scope.py`, which
171
+ is unaffected.
172
+ - **`attention.should_ping` / `push_pings` / `maybe_llm_rerank`** and the divergent
173
+ fleet-ping interval constant — the live nudge gate lives in the runner; the
174
+ attention copies were dead and drifting.
175
+ - Dead voicecall-brain probes (`connect()`/`hello()`), `dispatch` GREETING/GOODBYE
176
+ constants, and `scope.log_case`'s unwired logging sink.
177
+
178
+ ### Fixed (reliability)
179
+ - **Stale-429 false hold** — the 429 scan is now scoped to the current turn, so a
180
+ prior turn's rate-limit no longer falsely holds a healthy thread.
181
+ - **Silent cost-ledger overspend** — a record_cost write failure now emits a
182
+ rate-limited nudge / stderr signal instead of being swallowed, so the daily cap
183
+ can't be defeated with zero signal. The cost ledger is compacted by maintenance.
184
+ - **Voicecall teardown leaks** — `SessionManager.cancel_all()` cancels in-flight
185
+ bg sessions and releases `worker.lock` fds on call-end/evict; `evict()` awaits
186
+ the cancelled runner; the sessions dict is bounded; `config.load` raises
187
+ `ConfigError` instead of `SystemExit`.
188
+ - Quiet-hours honors the configured timezone (defensive `getattr` for callers
189
+ without a `timezone` field); PTT mode gets the transcribe watchdog; `listen`
190
+ uses `presence.runner_alive()` instead of the false-green pgrep probe.
191
+ - `send_nudge` extracted into `relay.notify` so the watchdog stops importing the
192
+ whole runner engine.
193
+
194
+ ### Changed
195
+ - `RELAY_THREADS_DIR` is `mkdir`-guarded; `runner.lock` records its PID; the
196
+ needs-eyes nudge count aligns to the freshly-changed subset; `_WAKE` regex and
197
+ the fleet state vocabulary are each single-sourced.
198
+
199
+ ## [1.0.0] — 2026-06-29
200
+
201
+ First standalone release, extracted from the bob monorepo into `phios-ai/relay`.
202
+
203
+ ### Added
204
+ - **Pip-installable package** `relay-cockpit` with the `relay` console script and
205
+ a `[voice]` extra for the Gemini-Live surface, `[llm]` for the optional routing
206
+ tiebreak.
207
+ - **`relay.config.settings`** — one env-overridable config rooted at `~/.relay`,
208
+ replacing the bob monorepo path anchoring.
209
+ - **`relay.thread_model`** — the de-vendored dir-per-thread Thread model, now a
210
+ first-class package (the old exec-based import landmine is gone).
211
+ - **Brain registry** (`relay.brain.registry`) — config-driven named brains in
212
+ `~/.relay/brains.yaml`, so a thread can target a local Claude, a ubob daemon, or
213
+ any remote Brain-Protocol endpoint by name.
214
+ - **Per-user scoping** (`RELAY_USER`) — a shared fleet no longer cross-shows
215
+ another person's threads.
216
+ - **`sessionStatus`** — an additive Brain-Protocol v1 method + a remote fleet
217
+ source, so a client with no local thread filesystem (phone, web, another
218
+ machine) can render the board by querying the brain. This is the "use it
219
+ online" path.
220
+
221
+ ### Changed
222
+ - The headless worker is spawned via `python -m relay.worker`; the notifier,
223
+ launchd label, and all paths are config seams.
224
+
225
+ ### Notes
226
+ - Safety/permissions and real cost budgets live on the brain side for remote
227
+ brains — see [SECURITY.md](SECURITY.md).
@@ -0,0 +1,76 @@
1
+ # Contributing to yothere
2
+
3
+ yothere is a shipped product that other systems (bob, SIDES) depend on. Two people
4
+ build it, and neither should be able to break `main` alone. This is our working
5
+ agreement.
6
+
7
+ ## Golden rule
8
+
9
+ **Never commit to `main` directly.** Every change flows:
10
+
11
+ ```
12
+ branch → pull request → the OTHER founder approves → CI green → merge
13
+ ```
14
+
15
+ `main` is protected on GitHub: you cannot merge without one approval, and you
16
+ cannot approve your own PR. So the review is mutual by construction. Either Oscar
17
+ approves Phil's change, or Phil approves Oscar's, before more parts of the product
18
+ get changed.
19
+
20
+ ## What needs a real review vs a glance
21
+
22
+ Everything goes through a PR. How hard the reviewer looks depends on the change.
23
+
24
+ **Bigger changes — read the whole diff.** Anything touching:
25
+ - `src/yothere/` core (runner, watchdog, fleet, brain, auth, registry)
26
+ - the brain protocol (`docs/brain-protocol-v1.md` and the wire code that implements it)
27
+ - `deploy/` (Dockerfile, entrypoint, fly config)
28
+ - DB schema, migrations, or the auth store
29
+ - `.github/workflows/` (CI/release)
30
+ - `pyproject.toml`, dependencies, or anything that cuts a release/tag
31
+
32
+ For these, the reviewer reads the diff, checks that tests cover the change, and
33
+ explicitly flags any breaking change to the Brain Protocol or to a consumer
34
+ (bob, SIDES).
35
+
36
+ **Trivial changes — fast-approve on sight.** Docs, comments, CHANGELOG, README,
37
+ test-only additions. Still a PR (the gate always applies), but the review is a
38
+ quick look.
39
+
40
+ ## Before you request review (author)
41
+
42
+ - Tests pass locally:
43
+ ```bash
44
+ PYTHONPATH=src python tests/relay_test.py
45
+ # plus the feature suites you touched, e.g.
46
+ PYTHONPATH=src python tests/test_registry.py
47
+ ```
48
+ - CHANGELOG updated if the change is notable.
49
+ - PR is scoped small. Split unrelated changes into separate PRs.
50
+ - Fill in the PR template (it loads automatically).
51
+
52
+ ## When you review (reviewer)
53
+
54
+ Ask: does this break the Brain Protocol or another consumer? Are there tests?
55
+ Are migrations safe and reversible? Then **Approve**, **Request changes**, or
56
+ **Comment**. Resolve all conversations before merge (the gate requires it).
57
+
58
+ ## Merging
59
+
60
+ Squash-merge. The branch is auto-deleted after merge. `main` must be up to date
61
+ first (GitHub enforces this).
62
+
63
+ ## Emergency escape hatch
64
+
65
+ Admins can bypass the gate (`enforce_admins` is off) — this exists **only** for a
66
+ genuine emergency: prod is down, or a live security issue. If you bypass, you must:
67
+
68
+ 1. Post the diff to the other person immediately, and
69
+ 2. Open a retroactive PR the same day so the change is on the record.
70
+
71
+ The escape hatch is never for skipping review out of impatience. If you are
72
+ tempted to use it because the other person is asleep, wait or ping them.
73
+
74
+ ## Branch naming
75
+
76
+ `feat/…`, `fix/…`, `chore/…`, `docs/…`.
yothere-1.5.2/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Phil Wenger and Oscar
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
yothere-1.5.2/PKG-INFO ADDED
@@ -0,0 +1,201 @@
1
+ Metadata-Version: 2.4
2
+ Name: yothere
3
+ Version: 1.5.2
4
+ Summary: yothere — an ambient-agent cockpit: a voice loop + fleet board that drives any agent brain over the Brain Protocol and tells a human when to look.
5
+ Project-URL: Homepage, https://github.com/phios-ai/yothere
6
+ Project-URL: Repository, https://github.com/phios-ai/yothere
7
+ Author: Phil Wenger, Oscar
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Keywords: agents,ambient-agent,claude-code,cockpit,fleet,voice
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
19
+ Requires-Python: >=3.11
20
+ Requires-Dist: pywebpush>=1.14
21
+ Requires-Dist: pyyaml>=6
22
+ Requires-Dist: websockets>=12
23
+ Provides-Extra: dev
24
+ Requires-Dist: pyflakes>=3; extra == 'dev'
25
+ Requires-Dist: ruff>=0.6; extra == 'dev'
26
+ Provides-Extra: llm
27
+ Requires-Dist: litellm>=1.89; extra == 'llm'
28
+ Provides-Extra: mcp
29
+ Requires-Dist: mcp>=1.2; extra == 'mcp'
30
+ Provides-Extra: postgres
31
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
32
+ Provides-Extra: voice
33
+ Requires-Dist: deepgram-sdk>=3.7; extra == 'voice'
34
+ Requires-Dist: fastapi>=0.115; extra == 'voice'
35
+ Requires-Dist: google-genai>=1.75; extra == 'voice'
36
+ Requires-Dist: pipecat-ai-small-webrtc-prebuilt>=2.5; extra == 'voice'
37
+ Requires-Dist: pipecat-ai[deepgram,google,silero,webrtc]>=1.4; extra == 'voice'
38
+ Requires-Dist: twilio>=9.0; extra == 'voice'
39
+ Requires-Dist: uvicorn[standard]>=0.30; extra == 'voice'
40
+ Requires-Dist: wsproto>=1.2; extra == 'voice'
41
+ Provides-Extra: web
42
+ Requires-Dist: fastapi>=0.115; extra == 'web'
43
+ Requires-Dist: uvicorn[standard]>=0.30; extra == 'web'
44
+ Requires-Dist: wsproto>=1.2; extra == 'web'
45
+ Description-Content-Type: text/markdown
46
+
47
+ # yothere
48
+
49
+ **An ambient-agent cockpit.** yothere is the *interface* for long-running agents: a
50
+ fleet board, a voice loop, and a headless runner that advances work and tells a
51
+ human *when to look*. The agent that actually does the work, the **brain**, is
52
+ anything you point yothere at, over one published wire contract:
53
+ [`docs/brain-protocol-v1.md`](docs/brain-protocol-v1.md) (WebSocket + JSON-RPC 2.0).
54
+
55
+ yothere is harness-agnostic by design. The same cockpit drives:
56
+
57
+ - **Claude Code** running locally (`claude -p` per thread),
58
+ - **ubob** or any model-agnostic harness over its WebSocket daemon,
59
+ - a **remote brain** over the internet (a hosted sprite, a teammate's stack, a
60
+ work platform) — anything that speaks the Brain Protocol.
61
+
62
+ It runs the same whether the brain is on this machine or behind a `wss://`
63
+ endpoint, so you can start a thread at your desk and watch it from your phone.
64
+
65
+ > **New here?** Start with [`docs/ONBOARDING.md`](docs/ONBOARDING.md): a 20-minute
66
+ > walkthrough from install to a real task advancing, plus how to send feedback.
67
+
68
+ ## Why it exists
69
+
70
+ A fleet of agents working in the background is only useful if a human knows which
71
+ one needs them *now*. yothere is an **attention router for the human**, not a work
72
+ router for the agents: pull-on-glance by default, one rate-limited nudge ("N
73
+ threads need your eyes"), never a firehose, never interrupting your focus thread.
74
+
75
+ ## Install
76
+
77
+ ```bash
78
+ pip install yothere # core: fleet runner + board + remote-brain client
79
+ pip install 'yothere[voice]' # + the Gemini-Live voice surface (WebRTC/Twilio)
80
+ pip install 'yothere[llm]' # + optional LLM tiebreak for routing (deterministic otherwise)
81
+ ```
82
+
83
+ yothere keeps all of its state under `~/.yothere` (override with `YOTHERE_HOME`,
84
+ or the legacy `RELAY_HOME`; an existing `~/.relay` is used as a fallback); see
85
+ [`docs/configuration.md`](docs/configuration.md) for every env seam (each `RELAY_*`
86
+ name also accepts its `YOTHERE_*` sibling).
87
+
88
+ ## Quickstart — drive the bundled reference brain
89
+
90
+ ```bash
91
+ # 1. Start the conformance brain (the smallest valid Brain Protocol implementation).
92
+ python -m yothere.voicecall.echo_brain --port 9999 &
93
+
94
+ # 2. Point yothere at it and spawn a thread.
95
+ export RELAY_REMOTE_BRAIN_URL=ws://127.0.0.1:9999
96
+ export RELAY_THREAD_HARNESS=remote
97
+ yothere spawn "research agentic commerce"
98
+
99
+ # 3. Advance the fleet one tick, then glance at the board.
100
+ python -m yothere.runner once # or: python -m yothere.runner loop (always-on engine)
101
+ yothere board --open
102
+ ```
103
+
104
+ Swap `RELAY_REMOTE_BRAIN_URL` for your real endpoint and yothere drives your brain.
105
+ To run threads with a local Claude Code instead, set `RELAY_THREAD_HARNESS=claude`.
106
+
107
+ ## The contract
108
+
109
+ A brain implements [`docs/brain-protocol-v1.md`](docs/brain-protocol-v1.md):
110
+ `hello` / `streamSubscribe` / `prompt` / `cancel` / `close`, streaming back
111
+ `delta` (text), and optionally `progress`, `status` (drives the attention
112
+ router), and `cost`. The reference is
113
+ [`src/yothere/voicecall/echo_brain.py`](src/yothere/voicecall/echo_brain.py), ~60
114
+ lines. Two load-bearing caveats live in [`SECURITY.md`](SECURITY.md): a remote
115
+ brain's cost cap is advisory, and **the brain owns its own safety/permissions**.
116
+
117
+ ## Architecture
118
+
119
+ ```
120
+ yothere (cockpit + voice + runner) YOUR BRAIN
121
+ ┌───────────────────────────────────────────┐ ┌──────────────────┐
122
+ cli ──► spawn ──► store (dir-per-thread) ◄── runner ─┐ │
123
+ │ │ │ │ │ Brain │
124
+ board ◄─ fleet_state ◄──┘ brain_advance ──ws──► Protocol │
125
+ │ (attention router: rank · focus) │ │ v1 │
126
+ voice ──► session_manager ──────────────────────┘ └──────────────────┘
127
+ ```
128
+
129
+ - **store / thread_model** — the dir-per-thread state machine (atomic
130
+ `status.json`, session-id resume, the worker contract).
131
+ - **attention** — deterministic ranking + the human-facing guardrails.
132
+ - **runner / worker** — the headless advance engine (cost caps, stale sweep,
133
+ 429 usage-cap hold, coalesced nudges).
134
+ - **brain/** — the harness clients: local Claude, ubob daemon, any remote brain.
135
+ - **board / card** — the glance UI (server-rendered, XSS-safe).
136
+
137
+ ## Hosted mode (multi-tenant cockpit)
138
+
139
+ The `/overview` cockpit runs in one of two modes, selected by `RELAY_AUTH_MODE`:
140
+
141
+ - **`off` (default) — local, single-user.** No login. The page, its SSE stream
142
+ (`/live`), and the plan-review reply are reachable over loopback/tailnet
143
+ (`_PUBLIC_PATHS`); the dangerous surface (WebRTC offer, `/harness`, `/start`,
144
+ `/client`) stays behind the `RELAY_VOICECALL_BEARER` gate with a loopback
145
+ exemption. This is byte-identical to the pre-auth cockpit — zero config.
146
+ - **`hosted` — multi-tenant.** A login is required: the tailnet carve-out is gone,
147
+ every route except `/healthz` + `/login` needs a session cookie, and each account
148
+ is scoped to its **own** `~/.relay-<tenant>` home (separate threads/data/state),
149
+ so two logins see two isolated fleets with no cross-tenant read. Auth is
150
+ Python-native and stdlib-only (`yothere.voicecall.auth`): scrypt password hashing,
151
+ an opaque session token in SQLite (`RELAY_AUTH_DB`, WAL, separate from fleet
152
+ state), login lockout, and a `Secure` cookie tied to the mode (not the request
153
+ URL, so a TLS-terminating proxy can't drop it). Tenant homes live under
154
+ `RELAY_TENANTS_ROOT` (default: beside `~/.relay`).
155
+
156
+ Hosted env: `RELAY_AUTH_MODE=hosted`, `RELAY_AUTH_DB=<path>`,
157
+ `RELAY_TENANTS_ROOT=<dir>`. Seed users via `yothere.voicecall.auth.AuthStore.create_user`
158
+ (public signup + hosted deployment are the next increment). The headless runner is
159
+ still single-tenant in this increment: a non-default tenant's threads are created and
160
+ isolated, but advancing them needs per-tenant runner orchestration (next increment).
161
+
162
+ ## MCP surface
163
+
164
+ Drive a yothere fleet from any MCP client (Claude Desktop, etc.) — an orthogonal channel
165
+ that exposes the same front doors as the CLI and cockpit over stdio MCP:
166
+
167
+ - **`spawn_thread(task, mode, focus)`** — spawn fleet thread(s) from a natural-language
168
+ task (read/draft-only, blocks for your approval before any outward action).
169
+ - **`board()`** — the fleet at a glance, needs-eyes first.
170
+ - **`reply(thread_id, text)`** — approve / edit / reject a blocked thread.
171
+
172
+ Single-user / local (drives this machine's `~/.relay` fleet). `mcp` is an optional
173
+ extra; install + register:
174
+
175
+ ```bash
176
+ pip install 'yothere[mcp]'
177
+ ```
178
+
179
+ ```jsonc
180
+ // Claude Desktop: claude_desktop_config.json
181
+ { "mcpServers": { "yothere": { "command": "yothere-mcp" } } }
182
+ ```
183
+
184
+ The tool logic lives in SDK-free `_*_impl` helpers (`yothere.mcp_server`), so it imports
185
+ and tests without the `mcp` dependency installed.
186
+
187
+ ## Status
188
+
189
+ v1.0.0. Fleet runner, board, voice surface, and the remote-brain path are tested
190
+ (`python tests/relay_test.py`). The hosted web cockpit and brain-side token
191
+ lifecycle are out of v1 (see [`CHANGELOG.md`](CHANGELOG.md)).
192
+
193
+ ## Contributing
194
+
195
+ Two people build yothere and neither should break `main` alone. Every change goes
196
+ through a PR that the other founder approves before it merges — see
197
+ [`CONTRIBUTING.md`](CONTRIBUTING.md).
198
+
199
+ ## License
200
+
201
+ MIT — see [`LICENSE`](LICENSE).
@@ -0,0 +1,80 @@
1
+ # Quickstart
2
+
3
+ Five minutes from install to a thread advancing against a brain.
4
+
5
+ ## 1. Install
6
+
7
+ ```bash
8
+ pip install yothere
9
+ ```
10
+
11
+ ## 90-second start (no API key)
12
+
13
+ `yothere init` sets up `~/.yothere` (override with `YOTHERE_HOME`/`RELAY_HOME`;
14
+ an existing `~/.relay` is reused), detects any brain
15
+ you already have (a remote `RELAY_REMOTE_BRAIN_URL`, a `brains.yaml`, or the
16
+ `claude` CLI), and wires the bundled zero-key demo brain if you have none.
17
+ It is idempotent and never overwrites existing config.
18
+
19
+ ```bash
20
+ yothere init # one-time setup + a status block with your next commands
21
+ yothere init --demo # watch a live fleet end to end (~30s, zero keys)
22
+ yothere board --open # the board it just filled
23
+ ```
24
+
25
+ `--demo` starts a bundled Brain Protocol v1 brain on localhost, spawns one
26
+ thread ("Demo: competitive scan"), and advances it through the real
27
+ runner -> worker -> brain path: you'll see it stream progress, go **blocked**
28
+ asking for your eyes (the attention router at work), get replied to the way
29
+ you would (`yothere reply <id> "ship it"`), and finish **done** with a result
30
+ summary on the board. Everything stays under `RELAY_HOME`; Ctrl-C is safe.
31
+
32
+ ## 2. Run against the bundled reference brain
33
+
34
+ The `echo_brain` is the smallest valid Brain-Protocol implementation (~60 lines)
35
+ and the conformance fixture. Use it to prove the loop before wiring a real brain.
36
+
37
+ ```bash
38
+ # terminal 1 — the reference brain
39
+ python -m yothere.voicecall.echo_brain --port 9999
40
+
41
+ # terminal 2 — point yothere at it and drive a thread
42
+ export RELAY_REMOTE_BRAIN_URL=ws://127.0.0.1:9999
43
+ export RELAY_THREAD_HARNESS=remote
44
+ yothere spawn "summarize the state of agentic commerce"
45
+ python -m yothere.runner once # advance one tick
46
+ yothere board --open # glance at the fleet
47
+ ```
48
+
49
+ ## 3. Point at a real brain
50
+
51
+ Either set `RELAY_REMOTE_BRAIN_URL` (+ `RELAY_REMOTE_BRAIN_TOKEN`) to your
52
+ `wss://` endpoint, or register named brains in `~/.relay/brains.yaml` and select
53
+ one with `RELAY_BRAIN` (see [docs/configuration.md](docs/configuration.md)).
54
+
55
+ To run threads with a local Claude Code instead of a remote brain:
56
+
57
+ ```bash
58
+ export RELAY_THREAD_HARNESS=claude # needs the `claude` CLI on PATH
59
+ yothere spawn "draft the Q3 update"
60
+ python -m yothere.runner loop # always-on advance engine
61
+ ```
62
+
63
+ ## 4. Implement your own brain
64
+
65
+ Implement [docs/brain-protocol-v1.md](docs/brain-protocol-v1.md) (`hello`,
66
+ `streamSubscribe`, `prompt`, streaming `delta` frames). Add `progress` / `status`
67
+ / `cost` to light up the fleet cockpit fully, and `sessionStatus` so a stateless
68
+ client can render the board over the network. Read `echo_brain.py` for the shape.
69
+
70
+ ## Everyday commands
71
+
72
+ ```bash
73
+ yothere spawn "<task>" # create a thread (or `yothere preset <key> <arg>`)
74
+ yothere list # live threads + state
75
+ yothere show <id> # status, plan, transcript
76
+ yothere reply <id> "<text>" # answer a blocked thread -> it re-advances
77
+ yothere focus <id> # pin the focus thread (never auto-interrupted)
78
+ yothere board --open # the glance board
79
+ yothere note "<text>" # capture a reminder (no thread)
80
+ ```