alter-runtime 0.3.2__tar.gz → 0.4.8__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 (108) hide show
  1. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/.gitignore +12 -1
  2. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/PKG-INFO +32 -64
  3. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/README.md +29 -61
  4. alter_runtime-0.4.8/alter_runtime/__init__.py +10 -0
  5. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/adapters/__init__.py +9 -3
  6. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/adapters/claude_jsonl_watcher.py +30 -20
  7. alter_runtime-0.4.8/alter_runtime/adapters/contract.py +103 -0
  8. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/adapters/git_watcher.py +9 -9
  9. alter_runtime-0.4.8/alter_runtime/adapters/weave_watcher.py +546 -0
  10. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/adapters/worktree_watcher.py +20 -20
  11. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/cap_cache.py +37 -14
  12. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/cli.py +36 -25
  13. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/clients/token_usage_client.py +14 -19
  14. alter_runtime-0.4.8/alter_runtime/config.py +1154 -0
  15. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/consent.py +10 -11
  16. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/daemon.py +172 -119
  17. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/floor_loop.py +30 -31
  18. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/floor_preflight.py +63 -83
  19. alter_runtime-0.4.8/alter_runtime/http_auth.py +39 -0
  20. alter_runtime-0.4.8/alter_runtime/invocation_signing.py +620 -0
  21. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/notifiers/__init__.py +0 -4
  22. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/notifiers/desktop.py +3 -3
  23. alter_runtime-0.4.8/alter_runtime/sdk/__init__.py +9 -0
  24. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/sdk/client.py +23 -33
  25. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/service_install.py +5 -62
  26. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/services/__init__.py +2 -15
  27. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/services/launchd/com.alter.runtime.plist.in +8 -9
  28. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/services/systemd/alter-runtime.service.in +21 -8
  29. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/sockets/__init__.py +3 -3
  30. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/sockets/dbus.py +2 -2
  31. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/sockets/unix.py +20 -20
  32. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/__init__.py +12 -5
  33. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/active_sessions_cron_emitter.py +11 -11
  34. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/active_sessions_do_publisher.py +240 -83
  35. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/active_sessions_gc.py +2 -2
  36. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/active_sessions_writer.py +7 -10
  37. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/adapters_writer.py +12 -12
  38. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/agent_frames.py +53 -28
  39. alter_runtime-0.4.8/alter_runtime/subscribers/attunement_refresher.py +382 -0
  40. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/bus.py +8 -9
  41. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/cache_writer.py +27 -30
  42. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/ceremony_echo.py +20 -24
  43. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/do_sse.py +74 -50
  44. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/ebpf.py +6 -6
  45. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/inbox_writer.py +31 -33
  46. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/mcp_fallback.py +102 -27
  47. alter_runtime-0.4.8/alter_runtime/subscribers/presence_feed_writer.py +342 -0
  48. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/presence_writer.py +4 -4
  49. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/session_presence.py +45 -40
  50. alter_runtime-0.4.8/alter_runtime/subscribers/session_refresher.py +390 -0
  51. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/sse.py +7 -9
  52. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/subscribers/weave_intent_writer.py +34 -34
  53. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/update_loop.py +51 -62
  54. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/weave/__init__.py +3 -3
  55. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/alter_runtime/weave/resolver.py +34 -34
  56. alter_runtime-0.4.8/alter_runtime/weave_log.py +437 -0
  57. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/pyproject.toml +29 -23
  58. alter_runtime-0.3.2/alter_runtime/__init__.py +0 -11
  59. alter_runtime-0.3.2/alter_runtime/adapters/household/__init__.py +0 -29
  60. alter_runtime-0.3.2/alter_runtime/adapters/household/_base.py +0 -138
  61. alter_runtime-0.3.2/alter_runtime/adapters/household/compost/__init__.py +0 -17
  62. alter_runtime-0.3.2/alter_runtime/adapters/household/compost/adapter.py +0 -81
  63. alter_runtime-0.3.2/alter_runtime/adapters/household/compost/storage.py +0 -75
  64. alter_runtime-0.3.2/alter_runtime/adapters/household/compost/tests/test_adapter.py +0 -62
  65. alter_runtime-0.3.2/alter_runtime/adapters/household/compost/tests/test_storage.py +0 -23
  66. alter_runtime-0.3.2/alter_runtime/adapters/household/compost/tests/test_traits.py +0 -38
  67. alter_runtime-0.3.2/alter_runtime/adapters/household/compost/traits.py +0 -79
  68. alter_runtime-0.3.2/alter_runtime/adapters/household/self_hoster/__init__.py +0 -30
  69. alter_runtime-0.3.2/alter_runtime/adapters/household/self_hoster/adapter.py +0 -248
  70. alter_runtime-0.3.2/alter_runtime/adapters/household/self_hoster/storage.py +0 -83
  71. alter_runtime-0.3.2/alter_runtime/adapters/household/self_hoster/tests/__init__.py +0 -0
  72. alter_runtime-0.3.2/alter_runtime/adapters/household/self_hoster/tests/test_adapter.py +0 -216
  73. alter_runtime-0.3.2/alter_runtime/adapters/household/self_hoster/tests/test_storage.py +0 -25
  74. alter_runtime-0.3.2/alter_runtime/adapters/household/self_hoster/tests/test_traits.py +0 -55
  75. alter_runtime-0.3.2/alter_runtime/adapters/household/self_hoster/traits.py +0 -105
  76. alter_runtime-0.3.2/alter_runtime/adapters/household/tapo_ecosystem/__init__.py +0 -22
  77. alter_runtime-0.3.2/alter_runtime/adapters/household/tapo_ecosystem/adapter.py +0 -98
  78. alter_runtime-0.3.2/alter_runtime/adapters/household/tapo_ecosystem/storage.py +0 -95
  79. alter_runtime-0.3.2/alter_runtime/adapters/household/tapo_ecosystem/tests/__init__.py +0 -0
  80. alter_runtime-0.3.2/alter_runtime/adapters/household/tapo_ecosystem/tests/test_adapter.py +0 -55
  81. alter_runtime-0.3.2/alter_runtime/adapters/household/tapo_ecosystem/tests/test_storage.py +0 -28
  82. alter_runtime-0.3.2/alter_runtime/adapters/household/tapo_ecosystem/tests/test_traits.py +0 -45
  83. alter_runtime-0.3.2/alter_runtime/adapters/household/tapo_ecosystem/traits.py +0 -97
  84. alter_runtime-0.3.2/alter_runtime/adapters/household/workshop_tools/__init__.py +0 -25
  85. alter_runtime-0.3.2/alter_runtime/adapters/household/workshop_tools/adapter.py +0 -77
  86. alter_runtime-0.3.2/alter_runtime/adapters/household/workshop_tools/storage.py +0 -92
  87. alter_runtime-0.3.2/alter_runtime/adapters/household/workshop_tools/tests/__init__.py +0 -0
  88. alter_runtime-0.3.2/alter_runtime/adapters/household/workshop_tools/tests/test_adapter.py +0 -48
  89. alter_runtime-0.3.2/alter_runtime/adapters/household/workshop_tools/tests/test_storage.py +0 -26
  90. alter_runtime-0.3.2/alter_runtime/adapters/household/workshop_tools/tests/test_traits.py +0 -45
  91. alter_runtime-0.3.2/alter_runtime/adapters/household/workshop_tools/traits.py +0 -95
  92. alter_runtime-0.3.2/alter_runtime/atlas/__init__.py +0 -47
  93. alter_runtime-0.3.2/alter_runtime/atlas/base.py +0 -102
  94. alter_runtime-0.3.2/alter_runtime/atlas/ledger.py +0 -196
  95. alter_runtime-0.3.2/alter_runtime/atlas/observations.py +0 -136
  96. alter_runtime-0.3.2/alter_runtime/atlas/schema.py +0 -106
  97. alter_runtime-0.3.2/alter_runtime/clients/__init__.py +0 -0
  98. alter_runtime-0.3.2/alter_runtime/config.py +0 -648
  99. alter_runtime-0.3.2/alter_runtime/http_auth.py +0 -173
  100. alter_runtime-0.3.2/alter_runtime/sdk/__init__.py +0 -12
  101. alter_runtime-0.3.2/alter_runtime/services/systemd/cf-access-env.conf.in +0 -29
  102. alter_runtime-0.3.2/docs/schemas/README.md +0 -46
  103. alter_runtime-0.3.2/examples/awesomewm/README.md +0 -122
  104. alter_runtime-0.3.2/examples/waybar/README.md +0 -66
  105. alter_runtime-0.3.2/pypi-truealter/README.md +0 -46
  106. alter_runtime-0.3.2/pypi-truealter/pyproject.toml +0 -67
  107. {alter_runtime-0.3.2 → alter_runtime-0.4.8}/LICENSE +0 -0
  108. {alter_runtime-0.3.2/alter_runtime/adapters/household/compost/tests → alter_runtime-0.4.8/alter_runtime/clients}/__init__.py +0 -0
@@ -22,7 +22,18 @@ htmlcov/
22
22
  .vscode/
23
23
  .idea/
24
24
 
25
- # Wave 2+ generated artefacts (local daemon state never in the repo)
25
+ # Generated daemon artefacts (local daemon state, never in the repo)
26
26
  *.sock
27
27
  keypair.json
28
28
  runtime.yaml
29
+
30
+ # Binary build outputs
31
+ out/
32
+ .pyinstaller-workdir/
33
+ .pyinstaller-dist/
34
+ .venv-pyinstaller/
35
+ *.spec.toc
36
+ *.toc
37
+ *.pkg
38
+ *.exe
39
+ *.app/
@@ -1,12 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: alter-runtime
3
- Version: 0.3.2
4
- Summary: ~Alter Identity Runtime - local sovereign daemon for the continuous identity field. Subscribes to per-~handle Cloudflare Durable Objects, exposes Unix socket + D-Bus + CLI surfaces, falls back gracefully to direct MCP polling.
3
+ Version: 0.4.8
4
+ Summary: ~Alter Identity Runtime - local daemon for the continuous identity field. Subscribes to per-~handle Cloudflare Durable Objects, exposes Unix socket + D-Bus + CLI surfaces, falls back gracefully to direct MCP polling.
5
5
  Project-URL: Homepage, https://truealter.com
6
6
  Project-URL: Documentation, https://truealter.com/docs/alter-runtime
7
7
  Project-URL: Repository, https://github.com/true-alter/alter-runtime
8
8
  Project-URL: Bug Tracker, https://github.com/true-alter/alter-runtime/issues
9
- Author: Blake Morrison
9
+ Author: ~Alter
10
10
  Author-email: ALTER Meridian Pty Ltd <hello@truealter.com>
11
11
  License: Apache-2.0
12
12
  License-File: LICENSE
@@ -68,13 +68,10 @@ Description-Content-Type: text/markdown
68
68
 
69
69
  # alter-runtime - ~Alter Identity Runtime
70
70
 
71
- > **L3 of the six-layer identity distribution surface.** The local sovereign daemon that
72
- > lives alongside you, subscribes to your per-`~handle` Cloudflare Durable Object, and
73
- > renders your identity field across every surface your device touches - terminal, IDE,
74
- > status bars, desktop tray, browser extension. Falls back gracefully to direct MCP
75
- > polling when the edge is unreachable.
76
-
77
- The alter-runtime daemon architecture was established in April 2026.
71
+ > The local daemon that lives alongside you, subscribes to your per-`~handle`
72
+ > Cloudflare Durable Object, and renders your identity field across every surface
73
+ > your device touches - terminal, IDE, status bars, desktop tray, browser extension.
74
+ > Falls back gracefully to direct MCP polling when the edge is unreachable.
78
75
 
79
76
  ## Alpha release - filesystem key storage
80
77
 
@@ -100,7 +97,7 @@ documented fallback.
100
97
  compromise would imply identity compromise - pair the runtime with
101
98
  hardware-attested passkey registration via the browser claim flow (graduated
102
99
  attestation). The filesystem-stored device key then scopes only to ambient
103
- signal ingestion, not to Sovereign-tier authorisations, which require a fresh
100
+ signal ingestion, not to high-assurance authorisations, which require a fresh
104
101
  hardware passkey ceremony per session.
105
102
 
106
103
  **Reporting issues.** Security-relevant reports: please follow
@@ -110,16 +107,9 @@ filing a public issue. Non-security bugs and feature requests: email
110
107
 
111
108
  ## Status
112
109
 
113
- | Wave | Stream | Status |
114
- |------|--------|--------|
115
- | 1 | 1c - Daemon skeleton + `AlterClient` SDK | Shipped |
116
- | 2 | 2b - Subscribers, Unix socket, D-Bus, git watcher | Shipped |
117
- | 2 | 2c - systemd + launchd service units | Shipped |
118
- | 2 | 2d - First pixel: CC hook + scripts upgrade | Shipped |
119
- | 2 | 2e - eBPF subscriber (reference impl in `alter-ebpf`) | Shipped |
120
- | 3 | 3a - Cross-platform tray surfaces | Planned |
121
- | 3 | 3b - Windows Service + `pam_alter` stub | Planned |
122
- | 3 | 3c - PyPI release CI + signed binaries | Planned |
110
+ Shipped: the daemon and `AlterClient` SDK; subscribers, the Unix socket,
111
+ D-Bus, and the git watcher; systemd and launchd service units; the local
112
+ editor and shell hook surfaces; and the eBPF subscriber.
123
113
 
124
114
  ## Install
125
115
 
@@ -167,7 +157,7 @@ alter-runtime stop
167
157
  at `https://mcp.truealter.com/events/~yourhandle/stream`. Events arrive with ~50ms
168
158
  latency worldwide.
169
159
 
170
- 2. **Falls back gracefully** to direct polling of the ALTER MCP endpoint at
160
+ 2. **Falls back gracefully** to direct polling of the `~alter` MCP endpoint at
171
161
  `https://api.truealter.com/api/v1/mcp` when the DO is unreachable for more than 3
172
162
  seconds. Your surfaces never know which path served them.
173
163
 
@@ -176,18 +166,18 @@ alter-runtime stop
176
166
  `~/Library/Application Support/alter/runtime.sock` (macOS) - line-delimited JSON
177
167
  - **D-Bus** interface `org.alter.Identity1` on the session bus (Linux) - used by
178
168
  GNOME/KDE/Waybar modules
179
- - **HTTP/SSE** loopback at `http://127.0.0.1:<port>/events` - used by the CC hook
180
- and shell scripts
169
+ - **HTTP/SSE** loopback at `http://127.0.0.1:<port>/events` - used by local
170
+ editor hooks and shell scripts
181
171
 
182
- 4. **Collects ambient signals** via adapters (Wave 2):
172
+ 4. **Collects ambient signals** via adapters:
183
173
  - Git commits, branch switches, pushes (via `watchdog` on `.git/refs/heads/`)
184
- - CC hook events (forwarded from `.claude/hooks/*.sh`)
174
+ - Editor session hook events (forwarded from local shell hooks)
185
175
  - Shell command invocations (opt-in)
186
- - eBPF kernel attestations (shipped; reference impl in `alter-ebpf`)
176
+ - eBPF kernel attestations (shipped)
187
177
 
188
178
  5. **Maintains a local cache** of your last-known-good field state so the first-paint
189
- tilde warmth renders in <1 second, even offline. Per IFA's Five OS-Native Properties,
190
- this is the **monotonic continuity** guarantee.
179
+ tilde warmth renders in <1 second, even offline. The cached state always renders,
180
+ so your surfaces stay continuous across restarts and network drops.
191
181
 
192
182
  ## Layout
193
183
 
@@ -198,21 +188,21 @@ alter-runtime/
198
188
  ├── LICENSE
199
189
  ├── alter_runtime/
200
190
  │ ├── __init__.py
201
- │ ├── config.py # XDG loader, reads alter-cli session.json
191
+ │ ├── config.py # XDG loader, reads the CLI session.json
202
192
  │ ├── daemon.py # asyncio supervisor
203
193
  │ ├── cli.py # argparse entrypoint: init|start|stop|status|query|ingest|daemon
204
194
  │ ├── sdk/
205
195
  │ │ ├── __init__.py
206
196
  │ │ └── client.py # AlterClient (async identity MCP client)
207
- │ ├── subscribers/ # (Wave 2)
208
- │ │ ├── do_sse.py # primary - subscribes to handle-alter DO SSE
197
+ │ ├── subscribers/
198
+ │ │ ├── do_sse.py # primary - subscribes to the per-handle DO SSE stream
209
199
  │ │ └── mcp_fallback.py # fallback - polls api.truealter.com/api/v1/mcp
210
- │ ├── sockets/ # (Wave 2)
200
+ │ ├── sockets/
211
201
  │ │ ├── unix.py # /run/user/$UID/alter.sock
212
202
  │ │ └── dbus.py # org.alter.Identity1
213
- │ ├── adapters/ # (Wave 2)
203
+ │ ├── adapters/
214
204
  │ │ └── git_watcher.py # watchdog on .git/refs/heads/
215
- │ └── services/ # (Wave 2)
205
+ │ └── services/
216
206
  │ ├── systemd/alter-runtime.service
217
207
  │ ├── launchd/com.alter.runtime.plist
218
208
  │ └── windows/AlterRuntimeService.py
@@ -249,41 +239,19 @@ asyncio.run(main())
249
239
 
250
240
  ## Packaging
251
241
 
252
- - **PyPI** - `pip install alter-runtime` (publish is tag-gated; see
253
- `.github/workflows/ci.yml`).
254
- - **AUR** - draft PKGBUILD at [`aur/PKGBUILD`](aur/PKGBUILD). Submission is
255
- blocked on the PyPI publish resolving the sdist URL. Regenerate `.SRCINFO`
256
- with `makepkg --printsrcinfo > .SRCINFO` before AUR upload.
257
- - **`truealter` PyPI shim** - thin console-script package at
258
- [`pypi-truealter/`](pypi-truealter/) that execs the npm-installed
259
- `alter` binary. Distinct from `alter-runtime`: `pip install truealter`
260
- gets pip-centric users a fourth CLI install channel alongside npm,
261
- Homebrew (planned), and the AUR (planned). Tag format
262
- `truealter-v<version>`; see
263
- [`.github/workflows/truealter-publish.yml`](.github/workflows/truealter-publish.yml).
264
-
265
- ## Contributing
266
-
267
- After cloning, wire the identity-trailer hook so commits land with `Acted-By:`, `Drafted-With:`, and `Co-Authored-By:` trailers automatically:
268
-
269
- ```
270
- bash scripts/setup-githooks.sh
271
- ```
272
-
273
- The helper sets the local `core.hooksPath` to `.githooks/` and marks every hook executable. It is idempotent - safe to re-run after pulls. Equivalent one-liner without the helper:
274
-
275
- ```
276
- git config core.hooksPath .githooks
277
- ```
242
+ `alter-runtime` is distributed through several install channels:
278
243
 
279
- The hook reads your ALTER session at `~/.config/alter/session.json` (run `alter login` from the [`@truealter/cli`](https://www.npmjs.com/package/@truealter/cli) once) plus `$CLAUDE_MODEL` for the Instrument tier attribution. It is silent when the session is missing or `jq` is unavailable, so a contributor without an ALTER session still gets a normal commit; the CI verifier catches missing trailers at PR time (warn-only during the Rung 1/2 migration window).
244
+ - **PyPI** - `pip install alter-runtime`
245
+ - **Arch Linux (AUR)** - `alter-runtime`
246
+ - **Homebrew** - `brew install alter-runtime`
280
247
 
281
- Python projects don't have npm's `prepare`-on-install lifecycle, so this step is manual - done once after clone and persisted in your local `.git/config`.
248
+ Pin an exact version in downstream tooling during the alpha and read the
249
+ CHANGELOG before upgrading.
282
250
 
283
251
  ## Related projects
284
252
 
285
253
  - [`@truealter/sdk`](https://www.npmjs.com/package/@truealter/sdk) - TypeScript
286
254
  SDK client for the public MCP server.
287
- - ALTER MCP endpoint - `https://mcp.truealter.com` (SSE per-`~handle` stream)
255
+ - `~alter` MCP endpoint - `https://mcp.truealter.com` (SSE per-`~handle` stream)
288
256
  and `https://api.truealter.com/api/v1/mcp` (HTTP fallback). Both are documented
289
257
  at [truealter.com](https://truealter.com).
@@ -1,12 +1,9 @@
1
1
  # alter-runtime - ~Alter Identity Runtime
2
2
 
3
- > **L3 of the six-layer identity distribution surface.** The local sovereign daemon that
4
- > lives alongside you, subscribes to your per-`~handle` Cloudflare Durable Object, and
5
- > renders your identity field across every surface your device touches - terminal, IDE,
6
- > status bars, desktop tray, browser extension. Falls back gracefully to direct MCP
7
- > polling when the edge is unreachable.
8
-
9
- The alter-runtime daemon architecture was established in April 2026.
3
+ > The local daemon that lives alongside you, subscribes to your per-`~handle`
4
+ > Cloudflare Durable Object, and renders your identity field across every surface
5
+ > your device touches - terminal, IDE, status bars, desktop tray, browser extension.
6
+ > Falls back gracefully to direct MCP polling when the edge is unreachable.
10
7
 
11
8
  ## Alpha release - filesystem key storage
12
9
 
@@ -32,7 +29,7 @@ documented fallback.
32
29
  compromise would imply identity compromise - pair the runtime with
33
30
  hardware-attested passkey registration via the browser claim flow (graduated
34
31
  attestation). The filesystem-stored device key then scopes only to ambient
35
- signal ingestion, not to Sovereign-tier authorisations, which require a fresh
32
+ signal ingestion, not to high-assurance authorisations, which require a fresh
36
33
  hardware passkey ceremony per session.
37
34
 
38
35
  **Reporting issues.** Security-relevant reports: please follow
@@ -42,16 +39,9 @@ filing a public issue. Non-security bugs and feature requests: email
42
39
 
43
40
  ## Status
44
41
 
45
- | Wave | Stream | Status |
46
- |------|--------|--------|
47
- | 1 | 1c - Daemon skeleton + `AlterClient` SDK | Shipped |
48
- | 2 | 2b - Subscribers, Unix socket, D-Bus, git watcher | Shipped |
49
- | 2 | 2c - systemd + launchd service units | Shipped |
50
- | 2 | 2d - First pixel: CC hook + scripts upgrade | Shipped |
51
- | 2 | 2e - eBPF subscriber (reference impl in `alter-ebpf`) | Shipped |
52
- | 3 | 3a - Cross-platform tray surfaces | Planned |
53
- | 3 | 3b - Windows Service + `pam_alter` stub | Planned |
54
- | 3 | 3c - PyPI release CI + signed binaries | Planned |
42
+ Shipped: the daemon and `AlterClient` SDK; subscribers, the Unix socket,
43
+ D-Bus, and the git watcher; systemd and launchd service units; the local
44
+ editor and shell hook surfaces; and the eBPF subscriber.
55
45
 
56
46
  ## Install
57
47
 
@@ -99,7 +89,7 @@ alter-runtime stop
99
89
  at `https://mcp.truealter.com/events/~yourhandle/stream`. Events arrive with ~50ms
100
90
  latency worldwide.
101
91
 
102
- 2. **Falls back gracefully** to direct polling of the ALTER MCP endpoint at
92
+ 2. **Falls back gracefully** to direct polling of the `~alter` MCP endpoint at
103
93
  `https://api.truealter.com/api/v1/mcp` when the DO is unreachable for more than 3
104
94
  seconds. Your surfaces never know which path served them.
105
95
 
@@ -108,18 +98,18 @@ alter-runtime stop
108
98
  `~/Library/Application Support/alter/runtime.sock` (macOS) - line-delimited JSON
109
99
  - **D-Bus** interface `org.alter.Identity1` on the session bus (Linux) - used by
110
100
  GNOME/KDE/Waybar modules
111
- - **HTTP/SSE** loopback at `http://127.0.0.1:<port>/events` - used by the CC hook
112
- and shell scripts
101
+ - **HTTP/SSE** loopback at `http://127.0.0.1:<port>/events` - used by local
102
+ editor hooks and shell scripts
113
103
 
114
- 4. **Collects ambient signals** via adapters (Wave 2):
104
+ 4. **Collects ambient signals** via adapters:
115
105
  - Git commits, branch switches, pushes (via `watchdog` on `.git/refs/heads/`)
116
- - CC hook events (forwarded from `.claude/hooks/*.sh`)
106
+ - Editor session hook events (forwarded from local shell hooks)
117
107
  - Shell command invocations (opt-in)
118
- - eBPF kernel attestations (shipped; reference impl in `alter-ebpf`)
108
+ - eBPF kernel attestations (shipped)
119
109
 
120
110
  5. **Maintains a local cache** of your last-known-good field state so the first-paint
121
- tilde warmth renders in <1 second, even offline. Per IFA's Five OS-Native Properties,
122
- this is the **monotonic continuity** guarantee.
111
+ tilde warmth renders in <1 second, even offline. The cached state always renders,
112
+ so your surfaces stay continuous across restarts and network drops.
123
113
 
124
114
  ## Layout
125
115
 
@@ -130,21 +120,21 @@ alter-runtime/
130
120
  ├── LICENSE
131
121
  ├── alter_runtime/
132
122
  │ ├── __init__.py
133
- │ ├── config.py # XDG loader, reads alter-cli session.json
123
+ │ ├── config.py # XDG loader, reads the CLI session.json
134
124
  │ ├── daemon.py # asyncio supervisor
135
125
  │ ├── cli.py # argparse entrypoint: init|start|stop|status|query|ingest|daemon
136
126
  │ ├── sdk/
137
127
  │ │ ├── __init__.py
138
128
  │ │ └── client.py # AlterClient (async identity MCP client)
139
- │ ├── subscribers/ # (Wave 2)
140
- │ │ ├── do_sse.py # primary - subscribes to handle-alter DO SSE
129
+ │ ├── subscribers/
130
+ │ │ ├── do_sse.py # primary - subscribes to the per-handle DO SSE stream
141
131
  │ │ └── mcp_fallback.py # fallback - polls api.truealter.com/api/v1/mcp
142
- │ ├── sockets/ # (Wave 2)
132
+ │ ├── sockets/
143
133
  │ │ ├── unix.py # /run/user/$UID/alter.sock
144
134
  │ │ └── dbus.py # org.alter.Identity1
145
- │ ├── adapters/ # (Wave 2)
135
+ │ ├── adapters/
146
136
  │ │ └── git_watcher.py # watchdog on .git/refs/heads/
147
- │ └── services/ # (Wave 2)
137
+ │ └── services/
148
138
  │ ├── systemd/alter-runtime.service
149
139
  │ ├── launchd/com.alter.runtime.plist
150
140
  │ └── windows/AlterRuntimeService.py
@@ -181,41 +171,19 @@ asyncio.run(main())
181
171
 
182
172
  ## Packaging
183
173
 
184
- - **PyPI** - `pip install alter-runtime` (publish is tag-gated; see
185
- `.github/workflows/ci.yml`).
186
- - **AUR** - draft PKGBUILD at [`aur/PKGBUILD`](aur/PKGBUILD). Submission is
187
- blocked on the PyPI publish resolving the sdist URL. Regenerate `.SRCINFO`
188
- with `makepkg --printsrcinfo > .SRCINFO` before AUR upload.
189
- - **`truealter` PyPI shim** - thin console-script package at
190
- [`pypi-truealter/`](pypi-truealter/) that execs the npm-installed
191
- `alter` binary. Distinct from `alter-runtime`: `pip install truealter`
192
- gets pip-centric users a fourth CLI install channel alongside npm,
193
- Homebrew (planned), and the AUR (planned). Tag format
194
- `truealter-v<version>`; see
195
- [`.github/workflows/truealter-publish.yml`](.github/workflows/truealter-publish.yml).
196
-
197
- ## Contributing
198
-
199
- After cloning, wire the identity-trailer hook so commits land with `Acted-By:`, `Drafted-With:`, and `Co-Authored-By:` trailers automatically:
200
-
201
- ```
202
- bash scripts/setup-githooks.sh
203
- ```
204
-
205
- The helper sets the local `core.hooksPath` to `.githooks/` and marks every hook executable. It is idempotent - safe to re-run after pulls. Equivalent one-liner without the helper:
206
-
207
- ```
208
- git config core.hooksPath .githooks
209
- ```
174
+ `alter-runtime` is distributed through several install channels:
210
175
 
211
- The hook reads your ALTER session at `~/.config/alter/session.json` (run `alter login` from the [`@truealter/cli`](https://www.npmjs.com/package/@truealter/cli) once) plus `$CLAUDE_MODEL` for the Instrument tier attribution. It is silent when the session is missing or `jq` is unavailable, so a contributor without an ALTER session still gets a normal commit; the CI verifier catches missing trailers at PR time (warn-only during the Rung 1/2 migration window).
176
+ - **PyPI** - `pip install alter-runtime`
177
+ - **Arch Linux (AUR)** - `alter-runtime`
178
+ - **Homebrew** - `brew install alter-runtime`
212
179
 
213
- Python projects don't have npm's `prepare`-on-install lifecycle, so this step is manual - done once after clone and persisted in your local `.git/config`.
180
+ Pin an exact version in downstream tooling during the alpha and read the
181
+ CHANGELOG before upgrading.
214
182
 
215
183
  ## Related projects
216
184
 
217
185
  - [`@truealter/sdk`](https://www.npmjs.com/package/@truealter/sdk) - TypeScript
218
186
  SDK client for the public MCP server.
219
- - ALTER MCP endpoint - `https://mcp.truealter.com` (SSE per-`~handle` stream)
187
+ - `~alter` MCP endpoint - `https://mcp.truealter.com` (SSE per-`~handle` stream)
220
188
  and `https://api.truealter.com/api/v1/mcp` (HTTP fallback). Both are documented
221
189
  at [truealter.com](https://truealter.com).
@@ -0,0 +1,10 @@
1
+ """~Alter Identity Runtime.
2
+
3
+ The local daemon that renders your identity field across the surfaces your
4
+ device touches. See the package README for an overview.
5
+ """
6
+
7
+ from alter_runtime.sdk.client import AlterClient
8
+
9
+ __version__ = "0.4.8"
10
+ __all__ = ["AlterClient", "__version__"]
@@ -1,9 +1,9 @@
1
1
  """Local adapters - ambient-signal publishers.
2
2
 
3
3
  Adapters are the runtime's *inputs from the device itself*: they observe
4
- something local (git commits, CC hook invocations, shell activity) and
4
+ something local (git commits, editor-hook invocations, shell activity) and
5
5
  publish a signal onto the ``local.signal`` topic. A separate egress producer
6
- (W2.2d) is responsible for POSTing those signals back to the per-handle DO
6
+ is responsible for POSTing those signals back to the per-handle Durable Object
7
7
  ``/ingest`` endpoint so they become part of the continuous identity field.
8
8
 
9
9
  Adapters are deliberately kept simple: one file per signal source, no shared
@@ -14,6 +14,12 @@ runtime component.
14
14
 
15
15
  from alter_runtime.adapters.claude_jsonl_watcher import ClaudeJsonlWatcher
16
16
  from alter_runtime.adapters.git_watcher import GitWatcher
17
+ from alter_runtime.adapters.weave_watcher import WeaveWatcher
17
18
  from alter_runtime.adapters.worktree_watcher import WorktreeWatcher
18
19
 
19
- __all__ = ["ClaudeJsonlWatcher", "GitWatcher", "WorktreeWatcher"]
20
+ __all__ = [
21
+ "ClaudeJsonlWatcher",
22
+ "GitWatcher",
23
+ "WeaveWatcher",
24
+ "WorktreeWatcher",
25
+ ]
@@ -1,6 +1,7 @@
1
1
  """ClaudeJsonlWatcher - ambient token-usage adapter for Claude Code transcripts.
2
2
 
3
- Watches ``~/.claude/projects/<slug>/*.jsonl`` files with ``watchdog``.
3
+ Watches the per-project ``*.jsonl`` transcript files under Claude Code's
4
+ home projects directory with ``watchdog``.
4
5
  On modification, reads new lines from the persisted byte offset, parses each
5
6
  via the privacy-hard-stop ``parse_assistant_line`` function, and POSTs
6
7
  batches to the ALTER backend token-usage audit endpoint.
@@ -11,10 +12,9 @@ Privacy guarantee
11
12
  ``parse_assistant_line`` is the ONLY path from JSONL bytes to the wire.
12
13
  It is a strict **whitelist** parser: it constructs the output dict key-by-key
13
14
  from explicitly named fields and never spreads ``record``, ``message``, or
14
- ``usage``. The privacy regression test (``tests/test_claude_jsonl_watcher_no_content_leak.py``)
15
- seeds every non-whitelisted field with a CANARY sentinel and asserts that no
16
- sentinel reaches the serialised output. Any change to the whitelist fails
17
- that test.
15
+ ``usage``. A privacy regression test seeds every non-whitelisted field with a
16
+ CANARY sentinel and asserts that no sentinel reaches the serialised output.
17
+ Any change to the whitelist fails that test.
18
18
 
19
19
  Allowed output keys (exact set, no extras):
20
20
  - ``session_id`` - from top-level ``sessionId``
@@ -39,8 +39,8 @@ On file rotation (on-disk size < persisted offset), the offset is reset to 0.
39
39
  Backfill
40
40
  --------
41
41
 
42
- On first run, every existing ``*.jsonl`` under ``~/.claude/projects/`` is
43
- walked and assistant events within the last 30 days are emitted in batches of
42
+ On first run, every existing ``*.jsonl`` under the Claude Code projects
43
+ directory is walked and assistant events within the last 30 days are emitted in batches of
44
44
  at most 500. A 5-second sleep is inserted between batches to avoid flooding
45
45
  the backend.
46
46
 
@@ -57,18 +57,17 @@ Configuration
57
57
  The adapter is opt-in: register it only when ``config.enable_claude_jsonl_watcher``
58
58
  is ``True`` (default ``False``).
59
59
 
60
- Decisions
61
- ---------
60
+ Data handling
61
+ -------------
62
62
 
63
- - D-IaI-1 (Identity-as-Inference): token usage is a passive aggregate with
64
- k=1 (single principal machine). The inferred signal (burn-rate by model)
65
- is operational telemetry, NOT psychometric inference - no clause of the
66
- IaI 5-point test is triggered. Return: operator visibility.
63
+ - The adapter collects only aggregate token-usage counts per model
64
+ (input, output, and cache token totals) plus the message and session
65
+ identifiers listed above. It never reads or transmits prompt text,
66
+ response text, tool calls, or any message content. The data is local
67
+ operational telemetry for the owning user's own visibility.
67
68
  - HTTP auth: JWT sourced from ``load_session()`` at adapter start, then
68
69
  refreshed per-POST from ``ALTER_RUNTIME_SESSION_JWT`` env var as a
69
- fallback. Full auth substrate wiring (ensureFreshSession equivalent) is a
70
- Wave 2 follow-up - see ``TOKEN_USAGE_AUTH_GAP`` docstring on
71
- ``TokenUsageClient``.
70
+ fallback. See ``TokenUsageClient`` for details.
72
71
  """
73
72
 
74
73
  from __future__ import annotations
@@ -227,7 +226,7 @@ class ClaudeJsonlWatcher(Component):
227
226
  Shared :class:`EventBus` (not published to - present for Component
228
227
  symmetry and future local-signal emission).
229
228
  projects_dir:
230
- Override ``~/.claude/projects/`` for testing.
229
+ Override the Claude Code projects directory for testing.
231
230
  """
232
231
 
233
232
  name = "claude_jsonl_watcher"
@@ -237,9 +236,12 @@ class ClaudeJsonlWatcher(Component):
237
236
  config: DaemonConfig,
238
237
  bus: EventBus,
239
238
  projects_dir: Path | None = None,
239
+ *,
240
+ session_ref: object | None = None,
240
241
  ) -> None:
241
242
  self._config = config
242
243
  self._bus = bus
244
+ self._session_ref = session_ref # Optional SessionRef for live JWT read-through
243
245
  self._projects_dir = (projects_dir or CLAUDE_PROJECTS_DIR).expanduser().resolve()
244
246
  self._stop_event = asyncio.Event()
245
247
  self._loop: asyncio.AbstractEventLoop | None = None
@@ -265,11 +267,19 @@ class ClaudeJsonlWatcher(Component):
265
267
  jwt = session.jwt
266
268
 
267
269
  api_base = os.environ.get("ALTER_RUNTIME_API_BASE", "https://api.truealter.com")
270
+ _ref = self._session_ref
268
271
 
269
272
  def _jwt_provider() -> str | None:
270
- # Prefer env-var override (for future ensureFreshSession wiring);
273
+ # Prefer env-var override; then live SessionRef (proactive rotation);
271
274
  # fall back to the session JWT loaded at startup.
272
- return os.environ.get("ALTER_RUNTIME_SESSION_JWT") or jwt
275
+ env_jwt = os.environ.get("ALTER_RUNTIME_SESSION_JWT")
276
+ if env_jwt:
277
+ return env_jwt
278
+ if _ref is not None:
279
+ live = getattr(_ref, "current", None)
280
+ if live is not None:
281
+ return getattr(live, "jwt", None)
282
+ return jwt
273
283
 
274
284
  self._http_client = TokenUsageClient(base_url=api_base, jwt_provider=_jwt_provider)
275
285
 
@@ -502,7 +512,7 @@ def _read_events_from_file(
502
512
  def _slug_from_path(jsonl_path: Path, projects_dir: Path) -> str:
503
513
  """Derive the project slug from the JSONL file path.
504
514
 
505
- ``~/.claude/projects/<slug>/<session>.jsonl`` → ``<slug>``
515
+ ``<projects-dir>/<slug>/<session>.jsonl`` → ``<slug>``
506
516
  """
507
517
  try:
508
518
  relative = jsonl_path.relative_to(projects_dir)
@@ -0,0 +1,103 @@
1
+ """Adapter plugin contract, the public extension seam for runtime adapters.
2
+
3
+ Built-in adapters are registered directly in :mod:`alter_runtime.daemon`.
4
+ Out-of-tree adapters (for example gated or separately-installed adapters
5
+ kept in their own package) register under the ``alter_runtime.adapters``
6
+ entry-point group and are discovered at start-up. This keeps the daemon
7
+ free of any import of an adapter that lives outside the public tree: an
8
+ adapter the host has not installed is simply absent, and the public package
9
+ carries no reference to it.
10
+
11
+ A plugin entry-point resolves to a *factory*: a callable that accepts a
12
+ single :class:`AdapterContext` and returns one component, an iterable of
13
+ components, or ``None`` to decline registration (for example when the
14
+ adapter is disabled by config). A returned component is any object with an
15
+ async ``run`` method, matching :class:`alter_runtime.daemon.Component`.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import logging
21
+ from collections.abc import Callable, Iterable, Iterator
22
+ from dataclasses import dataclass
23
+ from importlib.metadata import entry_points
24
+ from typing import TYPE_CHECKING, Any
25
+
26
+ if TYPE_CHECKING:
27
+ from alter_runtime.daemon import Component
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+ #: Entry-point group external adapter packages register under.
32
+ ADAPTER_ENTRY_POINT_GROUP = "alter_runtime.adapters"
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class AdapterContext:
37
+ """Stable construction context handed to every adapter factory.
38
+
39
+ Passing a context object rather than positional arguments keeps the
40
+ factory signature stable as the runtime gains new wiring: new fields are
41
+ added here without changing existing plugins.
42
+ """
43
+
44
+ config: Any
45
+ event_bus: Any
46
+ session: Any = None
47
+
48
+
49
+ #: A factory takes an AdapterContext and returns component(s) or None.
50
+ AdapterFactory = Callable[["AdapterContext"], "Component | Iterable[Component] | None"]
51
+
52
+
53
+ def discover_adapter_components(context: AdapterContext) -> Iterator[Component]:
54
+ """Yield components contributed by installed adapter plugins.
55
+
56
+ Each entry-point under :data:`ADAPTER_ENTRY_POINT_GROUP` is loaded and
57
+ called with ``context``. Failures (import error, factory exception, a
58
+ non-component return) are logged and skipped so a single bad plugin can
59
+ never prevent the daemon from starting. When no plugin package is
60
+ installed the generator is empty and the daemon runs exactly as a clean
61
+ public build does.
62
+ """
63
+ try:
64
+ discovered = entry_points(group=ADAPTER_ENTRY_POINT_GROUP)
65
+ except Exception: # pragma: no cover - importlib metadata edge cases
66
+ logger.exception("failed to enumerate adapter entry-points")
67
+ return
68
+
69
+ for entry_point in discovered:
70
+ try:
71
+ factory = entry_point.load()
72
+ result = factory(context)
73
+ except Exception:
74
+ logger.exception("adapter plugin %r failed to load; skipping", entry_point.name)
75
+ continue
76
+ if result is None:
77
+ continue
78
+ candidates = result if _is_iterable(result) else [result]
79
+ for component in candidates:
80
+ if _looks_like_component(component):
81
+ logger.info("registered adapter plugin component: %s", entry_point.name)
82
+ yield component
83
+ else:
84
+ logger.warning(
85
+ "adapter plugin %r returned a non-component (%s); skipping",
86
+ entry_point.name,
87
+ type(component).__name__,
88
+ )
89
+
90
+
91
+ def _is_iterable(obj: object) -> bool:
92
+ if isinstance(obj, (str, bytes)):
93
+ return False
94
+ try:
95
+ iter(obj) # type: ignore[call-overload]
96
+ except TypeError:
97
+ return False
98
+ return True
99
+
100
+
101
+ def _looks_like_component(obj: object) -> bool:
102
+ run = getattr(obj, "run", None)
103
+ return callable(run)