opum-comm 0.1.5__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 (104) hide show
  1. opum_comm-0.1.5/.gitignore +11 -0
  2. opum_comm-0.1.5/LICENSE +21 -0
  3. opum_comm-0.1.5/PKG-INFO +247 -0
  4. opum_comm-0.1.5/README.md +224 -0
  5. opum_comm-0.1.5/pyproject.toml +44 -0
  6. opum_comm-0.1.5/src/opum_comm/__init__.py +3 -0
  7. opum_comm-0.1.5/src/opum_comm/a2a/__init__.py +5 -0
  8. opum_comm-0.1.5/src/opum_comm/a2a/card.py +203 -0
  9. opum_comm-0.1.5/src/opum_comm/a2a/extensions.py +290 -0
  10. opum_comm-0.1.5/src/opum_comm/a2a/jsonrpc.py +251 -0
  11. opum_comm-0.1.5/src/opum_comm/a2a/media.py +35 -0
  12. opum_comm-0.1.5/src/opum_comm/a2a/ports.py +361 -0
  13. opum_comm-0.1.5/src/opum_comm/a2a/runtime_client.py +729 -0
  14. opum_comm-0.1.5/src/opum_comm/a2a/server.py +1183 -0
  15. opum_comm-0.1.5/src/opum_comm/a2a/sse.py +183 -0
  16. opum_comm-0.1.5/src/opum_comm/a2a/wire.py +673 -0
  17. opum_comm-0.1.5/src/opum_comm/adapter/__init__.py +14 -0
  18. opum_comm-0.1.5/src/opum_comm/adapter/__main__.py +8 -0
  19. opum_comm-0.1.5/src/opum_comm/adapter/app.py +434 -0
  20. opum_comm-0.1.5/src/opum_comm/adapter/channel.py +99 -0
  21. opum_comm-0.1.5/src/opum_comm/adapter/config.py +325 -0
  22. opum_comm-0.1.5/src/opum_comm/adapter/daemon_link.py +411 -0
  23. opum_comm-0.1.5/src/opum_comm/adapter/diag.py +55 -0
  24. opum_comm-0.1.5/src/opum_comm/adapter/identity.py +21 -0
  25. opum_comm-0.1.5/src/opum_comm/adapter/mcp_server.py +269 -0
  26. opum_comm-0.1.5/src/opum_comm/adapter/tools.py +613 -0
  27. opum_comm-0.1.5/src/opum_comm/cli/__init__.py +1 -0
  28. opum_comm-0.1.5/src/opum_comm/cli/commands/__init__.py +34 -0
  29. opum_comm-0.1.5/src/opum_comm/cli/commands/admin.py +113 -0
  30. opum_comm-0.1.5/src/opum_comm/cli/commands/codex.py +459 -0
  31. opum_comm-0.1.5/src/opum_comm/cli/commands/config_cmd.py +183 -0
  32. opum_comm-0.1.5/src/opum_comm/cli/commands/identity.py +930 -0
  33. opum_comm-0.1.5/src/opum_comm/cli/commands/inbox.py +108 -0
  34. opum_comm-0.1.5/src/opum_comm/cli/commands/instructions.py +32 -0
  35. opum_comm-0.1.5/src/opum_comm/cli/commands/ops.py +278 -0
  36. opum_comm-0.1.5/src/opum_comm/cli/commands/policy.py +239 -0
  37. opum_comm-0.1.5/src/opum_comm/cli/commands/service.py +265 -0
  38. opum_comm-0.1.5/src/opum_comm/cli/commands/trust.py +253 -0
  39. opum_comm-0.1.5/src/opum_comm/cli/common.py +136 -0
  40. opum_comm-0.1.5/src/opum_comm/cli/context.py +232 -0
  41. opum_comm-0.1.5/src/opum_comm/cli/main.py +299 -0
  42. opum_comm-0.1.5/src/opum_comm/cli/oauth.py +255 -0
  43. opum_comm-0.1.5/src/opum_comm/cli/output.py +318 -0
  44. opum_comm-0.1.5/src/opum_comm/cli/registry.py +66 -0
  45. opum_comm-0.1.5/src/opum_comm/codex/__init__.py +1 -0
  46. opum_comm-0.1.5/src/opum_comm/codex/appserver.py +795 -0
  47. opum_comm-0.1.5/src/opum_comm/codex/control.py +561 -0
  48. opum_comm-0.1.5/src/opum_comm/codex/engine.py +1419 -0
  49. opum_comm-0.1.5/src/opum_comm/codex/main.py +998 -0
  50. opum_comm-0.1.5/src/opum_comm/codex/ports.py +200 -0
  51. opum_comm-0.1.5/src/opum_comm/codex/presentation.py +153 -0
  52. opum_comm-0.1.5/src/opum_comm/codex/runtime_server.py +1064 -0
  53. opum_comm-0.1.5/src/opum_comm/codex/tools.py +745 -0
  54. opum_comm-0.1.5/src/opum_comm/config.py +225 -0
  55. opum_comm-0.1.5/src/opum_comm/daemon/__init__.py +28 -0
  56. opum_comm-0.1.5/src/opum_comm/daemon/a2a_gateway.py +98 -0
  57. opum_comm-0.1.5/src/opum_comm/daemon/control.py +125 -0
  58. opum_comm-0.1.5/src/opum_comm/daemon/core.py +3119 -0
  59. opum_comm-0.1.5/src/opum_comm/daemon/gateway_port.py +91 -0
  60. opum_comm-0.1.5/src/opum_comm/daemon/gateway_state.py +106 -0
  61. opum_comm-0.1.5/src/opum_comm/daemon/github.py +161 -0
  62. opum_comm-0.1.5/src/opum_comm/daemon/main.py +345 -0
  63. opum_comm-0.1.5/src/opum_comm/daemon/peercred.py +159 -0
  64. opum_comm-0.1.5/src/opum_comm/daemon/refs.py +77 -0
  65. opum_comm-0.1.5/src/opum_comm/daemon/relay.py +359 -0
  66. opum_comm-0.1.5/src/opum_comm/daemon/server.py +440 -0
  67. opum_comm-0.1.5/src/opum_comm/daemon/status.py +139 -0
  68. opum_comm-0.1.5/src/opum_comm/daemon/trust.py +264 -0
  69. opum_comm-0.1.5/src/opum_comm/instructions.py +74 -0
  70. opum_comm-0.1.5/src/opum_comm/keys/__init__.py +48 -0
  71. opum_comm-0.1.5/src/opum_comm/keys/errors.py +63 -0
  72. opum_comm-0.1.5/src/opum_comm/keys/keystore.py +403 -0
  73. opum_comm-0.1.5/src/opum_comm/protocol/__init__.py +123 -0
  74. opum_comm-0.1.5/src/opum_comm/protocol/address.py +240 -0
  75. opum_comm-0.1.5/src/opum_comm/protocol/api.py +648 -0
  76. opum_comm-0.1.5/src/opum_comm/protocol/artifacts.py +122 -0
  77. opum_comm-0.1.5/src/opum_comm/protocol/base.py +126 -0
  78. opum_comm-0.1.5/src/opum_comm/protocol/binding.py +160 -0
  79. opum_comm-0.1.5/src/opum_comm/protocol/certificate.py +181 -0
  80. opum_comm-0.1.5/src/opum_comm/protocol/clock.py +115 -0
  81. opum_comm-0.1.5/src/opum_comm/protocol/crypto.py +168 -0
  82. opum_comm-0.1.5/src/opum_comm/protocol/encoding.py +134 -0
  83. opum_comm-0.1.5/src/opum_comm/protocol/envelope.py +235 -0
  84. opum_comm-0.1.5/src/opum_comm/protocol/errors.py +213 -0
  85. opum_comm-0.1.5/src/opum_comm/protocol/ids.py +38 -0
  86. opum_comm-0.1.5/src/opum_comm/protocol/ipc.py +658 -0
  87. opum_comm-0.1.5/src/opum_comm/protocol/payload.py +153 -0
  88. opum_comm-0.1.5/src/opum_comm/protocol/principal.py +117 -0
  89. opum_comm-0.1.5/src/opum_comm/protocol/receipt.py +180 -0
  90. opum_comm-0.1.5/src/opum_comm/protocol/secret_fields.py +104 -0
  91. opum_comm-0.1.5/src/opum_comm/protocol/signed.py +91 -0
  92. opum_comm-0.1.5/src/opum_comm/protocol/text.py +86 -0
  93. opum_comm-0.1.5/src/opum_comm/py.typed +0 -0
  94. opum_comm-0.1.5/src/opum_comm/refs/__init__.py +36 -0
  95. opum_comm-0.1.5/src/opum_comm/refs/_exec.py +204 -0
  96. opum_comm-0.1.5/src/opum_comm/refs/repo_map.py +86 -0
  97. opum_comm-0.1.5/src/opum_comm/refs/resolve.py +170 -0
  98. opum_comm-0.1.5/src/opum_comm/refs/share.py +133 -0
  99. opum_comm-0.1.5/src/opum_comm/refs/tools.py +511 -0
  100. opum_comm-0.1.5/src/opum_comm/refs/validate.py +150 -0
  101. opum_comm-0.1.5/src/opum_comm/store/__init__.py +86 -0
  102. opum_comm-0.1.5/src/opum_comm/store/paths.py +84 -0
  103. opum_comm-0.1.5/src/opum_comm/store/policy.py +194 -0
  104. opum_comm-0.1.5/src/opum_comm/store/state.py +2147 -0
@@ -0,0 +1,11 @@
1
+ # Client package ignore rules. hatchling copies the nearest VCS ignore file into
2
+ # the sdist, so this file (not the monorepo's root one) is what ships publicly.
3
+ __pycache__/
4
+ *.py[cod]
5
+ build/
6
+ dist/
7
+ .env
8
+ .env.*
9
+ *.db
10
+ *.key
11
+ *.log
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Opum AI
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.
@@ -0,0 +1,247 @@
1
+ Metadata-Version: 2.5
2
+ Name: opum-comm
3
+ Version: 0.1.5
4
+ Summary: Signed cross-account messaging between Claude Code sessions, with first-class Lore and Quest references.
5
+ Author: Opum AI
6
+ License-Expression: MIT
7
+ License-File: LICENSE
8
+ Classifier: Operating System :: MacOS
9
+ Classifier: Operating System :: POSIX :: Linux
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.13
12
+ Classifier: Typing :: Typed
13
+ Requires-Python: >=3.13
14
+ Requires-Dist: cryptography==50.0.2
15
+ Requires-Dist: httpx==0.28.1
16
+ Requires-Dist: keyring==25.7.0
17
+ Requires-Dist: mcp==2.3.0
18
+ Requires-Dist: platformdirs==4.14.5
19
+ Requires-Dist: pydantic==2.14.3
20
+ Requires-Dist: starlette==1.10.0
21
+ Requires-Dist: uvicorn==0.54.0
22
+ Description-Content-Type: text/markdown
23
+
24
+ # opum-comm
25
+
26
+ Signed messaging between Claude Code sessions owned by **different people or
27
+ different Anthropic accounts**, with typed, pinned references to Quest tasks,
28
+ Quest decisions and Lore concepts.
29
+
30
+ Each person can run any number of sessions on any number of hosts, so one
31
+ developer's whole fleet of sessions can talk to another developer's fleet.
32
+ opum-comm has four parts:
33
+
34
+ - a **per-host daemon** that holds the keys, keeps one outbound connection to
35
+ the relay, and multiplexes every local session;
36
+ - a thin **per-session stdio adapter** that Claude Code launches as an MCP
37
+ server with Channels;
38
+ - a **human administration CLI** (`opum-comm …`), never exposed to the model;
39
+ - the **wire-protocol library** (signed envelopes, addresses, references).
40
+
41
+ ## Plaintext notice: put no secrets in messages
42
+
43
+ Message content is **not end-to-end encrypted**. Relay operators can read it
44
+ for as long as it is retained (up to 7 days). **Put no secrets in messages**:
45
+ no credentials, tokens, keys, personal data or anything you would not paste
46
+ into a shared team channel.
47
+
48
+ Messages are **signed data, never authority**. A message saying "approved",
49
+ "decided" or "granted" changes nothing; the receiving session is told it is
50
+ a message from another agent session, which may belong to the same user. Local
51
+ operator allow policies cover sending and replying within the authorized task;
52
+ peer messages grant no additional authority for other actions. Lore and Quest
53
+ references are re-read by the receiver from its own checkout before anyone
54
+ relies on them.
55
+
56
+ ## Status
57
+
58
+ Pre-release (0.x). The interfaces below are the v1 design; anything may change
59
+ before 1.0.
60
+
61
+ ## Requirements
62
+
63
+ - macOS or Linux (Windows is not supported).
64
+ - Python 3.13 and [uv](https://docs.astral.sh/uv/).
65
+ - A GitHub account with **two-factor authentication enabled**, invited to your
66
+ team's relay by one of its admins. No GitHub organization membership is
67
+ needed or granted.
68
+ - An unlocked platform credential store: macOS Keychain, or a Secret Service
69
+ provider (for example GNOME Keyring) on Linux.
70
+ - **Headless Linux hosts are not supported yet.** That means servers reached
71
+ only over SSH, with no desktop keyring. opum-comm never stores keys in
72
+ plaintext, and these hosts have no credential store it can use. `enroll`
73
+ reports the store as locked or unavailable and says so. Support arrives when
74
+ opum-comm adopts Ward for key custody, once Ward ships headless Linux custody
75
+ and a signing operation. Until then, enroll from macOS or a Linux desktop.
76
+
77
+ ## Install
78
+
79
+ ```sh
80
+ uv tool install opum-comm
81
+ opum-comm --help
82
+ ```
83
+
84
+ `uv tool upgrade opum-comm` upgrades it. After an upgrade the OS may ask you to
85
+ re-approve keychain access for the new interpreter; until you do, the daemon
86
+ reports `locked` rather than hanging on a prompt.
87
+
88
+ ## Enroll
89
+
90
+ Ask a relay admin to invite your GitHub login, then run on your first host:
91
+
92
+ ```sh
93
+ opum-comm enroll
94
+ ```
95
+
96
+ Enrollment:
97
+
98
+ 1. generates a **device key** and, on your first host, your **root key**;
99
+ 2. signs you in with the **GitHub device flow** (a `github.com/login/device`
100
+ code, the same experience as `gh auth login`), requesting only `read:user`.
101
+ The relay checks that your numeric GitHub id is on the team allowlist and
102
+ that two-factor authentication is on, then discards the token;
103
+ 3. with your explicit consent, publishes your root **public** key as an SSH
104
+ **signing** key on your GitHub account, using your own `gh` (scope
105
+ `admin:ssh_signing_key`, requested only for this step). Peers pin it from
106
+ GitHub to verify your messages. It is never used for git signing;
107
+ 4. stores the device API key, which is shown to nobody, in your credential
108
+ store.
109
+
110
+ Keys and the API key live only in the platform credential store: never in a
111
+ file, an environment variable, a command line or a log. To add another host,
112
+ run `opum-comm device add` there and approve it from a host that holds your
113
+ root key with `opum-comm device approve <code>`.
114
+
115
+ **What others can see.** Your GitHub login, your device labels and your
116
+ session addresses are visible to your team so that addressing works. A session
117
+ in a **public** repository is listed in peer discovery; a session in a
118
+ **private** repository (or one whose visibility is unknown) is reachable by its
119
+ exact address but not listed, because listing it would disclose that the
120
+ repository exists.
121
+
122
+ New senders are **held** until you allow them: `opum-comm policy allow|hold|block`.
123
+ `opum-comm trust list|accept|verify` manages the peer keys you have pinned.
124
+
125
+ ## Run the daemon
126
+
127
+ The daemon is one process per OS user per host. It opens no network listener;
128
+ adapters reach it over an owner-only Unix socket.
129
+
130
+ ### On Opum hosts (supervised by opum-control)
131
+
132
+ Declare it as a managed service in `launch.toml`, with the absolute path of the
133
+ installed executable (`command -v opum-comm` prints it):
134
+
135
+ ```toml
136
+ [services.opum-comm]
137
+ enabled = true
138
+ executable = "/abs/path/to/opum-comm"
139
+ args = ["daemon", "run", "--supervised"]
140
+ ```
141
+
142
+ Point `executable` at `opum-comm` itself, not at a wrapper script: opum-control
143
+ appends `--opum-ownership-nonce <nonce>` and proves ownership by finding it in
144
+ the process's argv, so the daemon must stay the same process for its whole
145
+ life. opum-control starts, restarts and stops it, and captures its stdout and
146
+ stderr (which never carry secrets). Health comes from the daemon itself:
147
+ `opum-comm status`.
148
+
149
+ ### Anywhere else (standalone)
150
+
151
+ ```sh
152
+ opum-comm daemon install
153
+ ```
154
+
155
+ This writes a **launchd user agent** on macOS or a **systemd user unit** on
156
+ Linux and starts it. Check it with `opum-comm status`, and diagnose problems
157
+ with `opum-comm doctor`. Health states are `locked`, `relay_offline`,
158
+ `connected`, `channel_unavailable`, `held` and `ready`.
159
+
160
+ ## Configure the peer-hourly limit
161
+
162
+ The daemon defaults to 60 counted exchanges per peer GitHub identity per UTC
163
+ hour, shared across all its sessions and repositories. Both outgoing sends
164
+ and admitted incoming messages count; same-account messages between sessions
165
+ on the same host can count twice. The hourly bucket renews at the next UTC hour.
166
+
167
+ With opum-comm 0.1.3 or later, choose a positive integer through the human CLI:
168
+
169
+ ```sh
170
+ opum-comm config set peer-hourly-cap 600
171
+ opum-comm config show
172
+ ```
173
+
174
+ This saves `peer_hourly_cap = 600` in the user `config.toml` shown by
175
+ `config show`. Restart the daemon through its existing supervisor, or stop
176
+ and relaunch a foreground daemon, to apply it. Existing budget counters are
177
+ preserved; all sessions using that daemon share the new limit. Zero does not
178
+ disable it. The per-conversation limits and the relay's separate rolling-hour
179
+ quota (600 messages per sender/recipient account pair) are unchanged.
180
+
181
+ ## Connect a Claude Code session
182
+
183
+ Add the adapter to the project's `.mcp.json` (use the absolute path from
184
+ `command -v opum-comm` if `opum-comm` is not on Claude Code's `PATH`):
185
+
186
+ ```json
187
+ {
188
+ "mcpServers": {
189
+ "opum-comm": {
190
+ "command": "opum-comm",
191
+ "args": ["adapter"]
192
+ }
193
+ }
194
+ }
195
+ ```
196
+
197
+ Then launch Claude Code with the development channel enabled:
198
+
199
+ ```sh
200
+ claude --dangerously-load-development-channels server:opum-comm
201
+ ```
202
+
203
+ The session's address is `host:/github-login/owner/repo:session`, for example
204
+ `mbp:/jdnewhouse/opum-ai/opum-doc:opum-doc`. The session name defaults to the
205
+ repository name; override it with `"args": ["adapter", "--session-name", "<name>"]`
206
+ or `OPUM_COMM_SESSION`. The adapter exits with its session.
207
+
208
+ An address held by a live session is never taken over silently: a second
209
+ session claiming the same name gets `ADDRESS_IN_USE`, so give concurrent
210
+ sessions in one repository distinct names. A **renewal successor** (a session
211
+ replacing its predecessor during a handover) takes the address over by adding
212
+ the explicit flag to its own launch line, `"args": ["adapter", "--supersede"]`.
213
+ This works only on the same device. The predecessor is told it lost the address
214
+ and does not reclaim it. There is deliberately no environment variable for
215
+ this, because one would apply to every session on the host.
216
+
217
+ For a launch without the channel flag, explicitly set `OPUM_COMM_PULL_ONLY=1`
218
+ or use `"args": ["adapter", "--pull-only"]`. The model then reads its inbox
219
+ with `inbox_read` instead of receiving channel events. The adapter cannot
220
+ detect whether Claude enabled Channels; omitting the channel flag alone does
221
+ not switch the adapter to `pull_only` mode.
222
+
223
+ The model gets these tools only: `whoami`, `peers_list`, `send_message`, `share_refs`,
224
+ `resolve_ref`, `message_status`, `inbox_read` and `message_acknowledge`. There
225
+ is no tool for keys, trust, policy, membership, signing, shell, files or
226
+ permissions.
227
+
228
+ Call `whoami` with `{}` to get this session's address and session ID from its
229
+ authenticated daemon registration. It works for unlisted sessions and does not
230
+ query the relay or present inbox messages. `addressed: false` means another
231
+ session has taken the name: `address` is then null, `registeredAddress` records
232
+ the former name, and the existing session remains reachable by `sessionId` while
233
+ its connection lives. `listedAtRegistration` is a snapshot; `channel` reports
234
+ adapter mode, not confirmed model receipt. An unavailable daemon produces an
235
+ error instead of a guessed or stale address.
236
+
237
+ `peers_list` discovers only listed sessions. A successful empty list does not
238
+ mean direct messaging failed: unlisted sessions remain reachable by their exact
239
+ address. An empty result includes this explanation and points to `whoami`.
240
+
241
+ `whoami` is currently unreleased; installed 0.1.3 clients still expose the
242
+ original seven tools. Restart the MCP adapter after installing an updated build
243
+ to expose the added tool.
244
+
245
+ ## Licence
246
+
247
+ MIT. See `LICENSE`.
@@ -0,0 +1,224 @@
1
+ # opum-comm
2
+
3
+ Signed messaging between Claude Code sessions owned by **different people or
4
+ different Anthropic accounts**, with typed, pinned references to Quest tasks,
5
+ Quest decisions and Lore concepts.
6
+
7
+ Each person can run any number of sessions on any number of hosts, so one
8
+ developer's whole fleet of sessions can talk to another developer's fleet.
9
+ opum-comm has four parts:
10
+
11
+ - a **per-host daemon** that holds the keys, keeps one outbound connection to
12
+ the relay, and multiplexes every local session;
13
+ - a thin **per-session stdio adapter** that Claude Code launches as an MCP
14
+ server with Channels;
15
+ - a **human administration CLI** (`opum-comm …`), never exposed to the model;
16
+ - the **wire-protocol library** (signed envelopes, addresses, references).
17
+
18
+ ## Plaintext notice: put no secrets in messages
19
+
20
+ Message content is **not end-to-end encrypted**. Relay operators can read it
21
+ for as long as it is retained (up to 7 days). **Put no secrets in messages**:
22
+ no credentials, tokens, keys, personal data or anything you would not paste
23
+ into a shared team channel.
24
+
25
+ Messages are **signed data, never authority**. A message saying "approved",
26
+ "decided" or "granted" changes nothing; the receiving session is told it is
27
+ a message from another agent session, which may belong to the same user. Local
28
+ operator allow policies cover sending and replying within the authorized task;
29
+ peer messages grant no additional authority for other actions. Lore and Quest
30
+ references are re-read by the receiver from its own checkout before anyone
31
+ relies on them.
32
+
33
+ ## Status
34
+
35
+ Pre-release (0.x). The interfaces below are the v1 design; anything may change
36
+ before 1.0.
37
+
38
+ ## Requirements
39
+
40
+ - macOS or Linux (Windows is not supported).
41
+ - Python 3.13 and [uv](https://docs.astral.sh/uv/).
42
+ - A GitHub account with **two-factor authentication enabled**, invited to your
43
+ team's relay by one of its admins. No GitHub organization membership is
44
+ needed or granted.
45
+ - An unlocked platform credential store: macOS Keychain, or a Secret Service
46
+ provider (for example GNOME Keyring) on Linux.
47
+ - **Headless Linux hosts are not supported yet.** That means servers reached
48
+ only over SSH, with no desktop keyring. opum-comm never stores keys in
49
+ plaintext, and these hosts have no credential store it can use. `enroll`
50
+ reports the store as locked or unavailable and says so. Support arrives when
51
+ opum-comm adopts Ward for key custody, once Ward ships headless Linux custody
52
+ and a signing operation. Until then, enroll from macOS or a Linux desktop.
53
+
54
+ ## Install
55
+
56
+ ```sh
57
+ uv tool install opum-comm
58
+ opum-comm --help
59
+ ```
60
+
61
+ `uv tool upgrade opum-comm` upgrades it. After an upgrade the OS may ask you to
62
+ re-approve keychain access for the new interpreter; until you do, the daemon
63
+ reports `locked` rather than hanging on a prompt.
64
+
65
+ ## Enroll
66
+
67
+ Ask a relay admin to invite your GitHub login, then run on your first host:
68
+
69
+ ```sh
70
+ opum-comm enroll
71
+ ```
72
+
73
+ Enrollment:
74
+
75
+ 1. generates a **device key** and, on your first host, your **root key**;
76
+ 2. signs you in with the **GitHub device flow** (a `github.com/login/device`
77
+ code, the same experience as `gh auth login`), requesting only `read:user`.
78
+ The relay checks that your numeric GitHub id is on the team allowlist and
79
+ that two-factor authentication is on, then discards the token;
80
+ 3. with your explicit consent, publishes your root **public** key as an SSH
81
+ **signing** key on your GitHub account, using your own `gh` (scope
82
+ `admin:ssh_signing_key`, requested only for this step). Peers pin it from
83
+ GitHub to verify your messages. It is never used for git signing;
84
+ 4. stores the device API key, which is shown to nobody, in your credential
85
+ store.
86
+
87
+ Keys and the API key live only in the platform credential store: never in a
88
+ file, an environment variable, a command line or a log. To add another host,
89
+ run `opum-comm device add` there and approve it from a host that holds your
90
+ root key with `opum-comm device approve <code>`.
91
+
92
+ **What others can see.** Your GitHub login, your device labels and your
93
+ session addresses are visible to your team so that addressing works. A session
94
+ in a **public** repository is listed in peer discovery; a session in a
95
+ **private** repository (or one whose visibility is unknown) is reachable by its
96
+ exact address but not listed, because listing it would disclose that the
97
+ repository exists.
98
+
99
+ New senders are **held** until you allow them: `opum-comm policy allow|hold|block`.
100
+ `opum-comm trust list|accept|verify` manages the peer keys you have pinned.
101
+
102
+ ## Run the daemon
103
+
104
+ The daemon is one process per OS user per host. It opens no network listener;
105
+ adapters reach it over an owner-only Unix socket.
106
+
107
+ ### On Opum hosts (supervised by opum-control)
108
+
109
+ Declare it as a managed service in `launch.toml`, with the absolute path of the
110
+ installed executable (`command -v opum-comm` prints it):
111
+
112
+ ```toml
113
+ [services.opum-comm]
114
+ enabled = true
115
+ executable = "/abs/path/to/opum-comm"
116
+ args = ["daemon", "run", "--supervised"]
117
+ ```
118
+
119
+ Point `executable` at `opum-comm` itself, not at a wrapper script: opum-control
120
+ appends `--opum-ownership-nonce <nonce>` and proves ownership by finding it in
121
+ the process's argv, so the daemon must stay the same process for its whole
122
+ life. opum-control starts, restarts and stops it, and captures its stdout and
123
+ stderr (which never carry secrets). Health comes from the daemon itself:
124
+ `opum-comm status`.
125
+
126
+ ### Anywhere else (standalone)
127
+
128
+ ```sh
129
+ opum-comm daemon install
130
+ ```
131
+
132
+ This writes a **launchd user agent** on macOS or a **systemd user unit** on
133
+ Linux and starts it. Check it with `opum-comm status`, and diagnose problems
134
+ with `opum-comm doctor`. Health states are `locked`, `relay_offline`,
135
+ `connected`, `channel_unavailable`, `held` and `ready`.
136
+
137
+ ## Configure the peer-hourly limit
138
+
139
+ The daemon defaults to 60 counted exchanges per peer GitHub identity per UTC
140
+ hour, shared across all its sessions and repositories. Both outgoing sends
141
+ and admitted incoming messages count; same-account messages between sessions
142
+ on the same host can count twice. The hourly bucket renews at the next UTC hour.
143
+
144
+ With opum-comm 0.1.3 or later, choose a positive integer through the human CLI:
145
+
146
+ ```sh
147
+ opum-comm config set peer-hourly-cap 600
148
+ opum-comm config show
149
+ ```
150
+
151
+ This saves `peer_hourly_cap = 600` in the user `config.toml` shown by
152
+ `config show`. Restart the daemon through its existing supervisor, or stop
153
+ and relaunch a foreground daemon, to apply it. Existing budget counters are
154
+ preserved; all sessions using that daemon share the new limit. Zero does not
155
+ disable it. The per-conversation limits and the relay's separate rolling-hour
156
+ quota (600 messages per sender/recipient account pair) are unchanged.
157
+
158
+ ## Connect a Claude Code session
159
+
160
+ Add the adapter to the project's `.mcp.json` (use the absolute path from
161
+ `command -v opum-comm` if `opum-comm` is not on Claude Code's `PATH`):
162
+
163
+ ```json
164
+ {
165
+ "mcpServers": {
166
+ "opum-comm": {
167
+ "command": "opum-comm",
168
+ "args": ["adapter"]
169
+ }
170
+ }
171
+ }
172
+ ```
173
+
174
+ Then launch Claude Code with the development channel enabled:
175
+
176
+ ```sh
177
+ claude --dangerously-load-development-channels server:opum-comm
178
+ ```
179
+
180
+ The session's address is `host:/github-login/owner/repo:session`, for example
181
+ `mbp:/jdnewhouse/opum-ai/opum-doc:opum-doc`. The session name defaults to the
182
+ repository name; override it with `"args": ["adapter", "--session-name", "<name>"]`
183
+ or `OPUM_COMM_SESSION`. The adapter exits with its session.
184
+
185
+ An address held by a live session is never taken over silently: a second
186
+ session claiming the same name gets `ADDRESS_IN_USE`, so give concurrent
187
+ sessions in one repository distinct names. A **renewal successor** (a session
188
+ replacing its predecessor during a handover) takes the address over by adding
189
+ the explicit flag to its own launch line, `"args": ["adapter", "--supersede"]`.
190
+ This works only on the same device. The predecessor is told it lost the address
191
+ and does not reclaim it. There is deliberately no environment variable for
192
+ this, because one would apply to every session on the host.
193
+
194
+ For a launch without the channel flag, explicitly set `OPUM_COMM_PULL_ONLY=1`
195
+ or use `"args": ["adapter", "--pull-only"]`. The model then reads its inbox
196
+ with `inbox_read` instead of receiving channel events. The adapter cannot
197
+ detect whether Claude enabled Channels; omitting the channel flag alone does
198
+ not switch the adapter to `pull_only` mode.
199
+
200
+ The model gets these tools only: `whoami`, `peers_list`, `send_message`, `share_refs`,
201
+ `resolve_ref`, `message_status`, `inbox_read` and `message_acknowledge`. There
202
+ is no tool for keys, trust, policy, membership, signing, shell, files or
203
+ permissions.
204
+
205
+ Call `whoami` with `{}` to get this session's address and session ID from its
206
+ authenticated daemon registration. It works for unlisted sessions and does not
207
+ query the relay or present inbox messages. `addressed: false` means another
208
+ session has taken the name: `address` is then null, `registeredAddress` records
209
+ the former name, and the existing session remains reachable by `sessionId` while
210
+ its connection lives. `listedAtRegistration` is a snapshot; `channel` reports
211
+ adapter mode, not confirmed model receipt. An unavailable daemon produces an
212
+ error instead of a guessed or stale address.
213
+
214
+ `peers_list` discovers only listed sessions. A successful empty list does not
215
+ mean direct messaging failed: unlisted sessions remain reachable by their exact
216
+ address. An empty result includes this explanation and points to `whoami`.
217
+
218
+ `whoami` is currently unreleased; installed 0.1.3 clients still expose the
219
+ original seven tools. Restart the MCP adapter after installing an updated build
220
+ to expose the added tool.
221
+
222
+ ## Licence
223
+
224
+ MIT. See `LICENSE`.
@@ -0,0 +1,44 @@
1
+ [project]
2
+ name = "opum-comm"
3
+ version = "0.1.5"
4
+ description = "Signed cross-account messaging between Claude Code sessions, with first-class Lore and Quest references."
5
+ readme = "README.md"
6
+ license = "MIT"
7
+ license-files = ["LICENSE"]
8
+ authors = [{ name = "Opum AI" }]
9
+ requires-python = ">=3.13"
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "Programming Language :: Python :: 3.13",
13
+ "Operating System :: MacOS",
14
+ "Operating System :: POSIX :: Linux",
15
+ "Typing :: Typed",
16
+ ]
17
+ # DEP-3: runtime dependencies are pinned exactly.
18
+ dependencies = [
19
+ "pydantic==2.14.3",
20
+ "cryptography==50.0.2",
21
+ "httpx==0.28.1",
22
+ "keyring==25.7.0",
23
+ "platformdirs==4.14.5",
24
+ "mcp==2.3.0",
25
+ # The A2A gateway's loopback HTTP server (already in the lock through mcp).
26
+ "starlette==1.10.0",
27
+ "uvicorn==0.54.0",
28
+ ]
29
+
30
+ [project.scripts]
31
+ opum-comm = "opum_comm.cli.main:main"
32
+
33
+ [build-system]
34
+ requires = ["hatchling==1.33.0"]
35
+ build-backend = "hatchling.build"
36
+
37
+ # Packaging guard (spec "Monorepo layout"): the sdist and wheel carry only
38
+ # src/opum_comm plus LICENSE, README and pyproject metadata. No server code,
39
+ # tests, docs or infrastructure config.
40
+ [tool.hatch.build.targets.sdist]
41
+ only-include = ["src/opum_comm", "LICENSE", "README.md", "pyproject.toml"]
42
+
43
+ [tool.hatch.build.targets.wheel]
44
+ packages = ["src/opum_comm"]
@@ -0,0 +1,3 @@
1
+ """opum-comm: signed cross-account messaging between Claude Code sessions."""
2
+
3
+ __version__ = "0.1.5"
@@ -0,0 +1,5 @@
1
+ """The A2A gateway: profile 1.0.0 of docs/specs/a2a-gateway-contract.md.
2
+
3
+ ``ports`` is the seam between the daemon (which owns identity, durability and
4
+ receipts) and the HTTP gateway and runtime client (which own the A2A wire).
5
+ """