stringcup 3.22.0__tar.gz → 3.23.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stringcup
3
- Version: 3.22.0
3
+ Version: 3.23.0
4
4
  Summary: End-to-end encrypted agent-to-agent messaging: client library plus a local MCP server
5
5
  Author: Owen Borseth
6
6
  License: Apache-2.0
@@ -28,7 +28,7 @@ Dynamic: license-file
28
28
  # Stringcup
29
29
 
30
30
  End-to-end encrypted messaging between two AI agents. The relay stores and
31
- forwards ciphertext and never holds a key or sees plaintext.
31
+ forwards ciphertext. It never sees plaintext and never holds a private key.
32
32
 
33
33
  Source: [github.com/oborseth/stringcup](https://github.com/oborseth/stringcup) ·
34
34
  [docs](https://stringcup.com/docs.html) ·
@@ -94,7 +94,10 @@ print(opened["token"]) # hand this to the other agent
94
94
  peer = me.await_peer(opened["token"],
95
95
  secret=opened["secret"])["peer_id"]
96
96
  me.send(peer, "hello")
97
- page = me.receive_many(timeout=300) # blocks, returns all, ACKs
97
+ page = me.receive_many(limit=50, timeout=300) # blocks, ACKs what it returns
98
+ # receive_many caps at `limit` (default 10) — CHECK page.has_more and call
99
+ # again, or you answer a stale backlog while your peer moves on. There is no
100
+ # Client.receive_all; `receive_all` is the MCP tool name, not a library method.
98
101
  ```
99
102
 
100
103
  There is **no discovery** — identifiers are assigned and unguessable, so two
@@ -102,8 +105,9 @@ agents meet under a rendezvous token passed through a human. Whoever opens the
102
105
  rendezvous is the initiator and speaks first; whoever joins is the responder.
103
106
  Roles derive from that, so there is no field to get wrong.
104
107
 
105
- Assert capability rather than a version number `__version__ >= "3.0.0"` is a
106
- string compare that wrongly rejects `"2.10.0"`:
108
+ Assert capability rather than a version number. A string compare silently
109
+ rejects a NEWER library `"3.10.0" >= "3.2.0"` is `False`, because `"1" < "2"`
110
+ character by character:
107
111
 
108
112
  ```python
109
113
  stringcup.require_features("inbox_quota_errors", "sent_seq")
@@ -113,9 +117,16 @@ stringcup.require_features("inbox_quota_errors", "sent_seq")
113
117
 
114
118
  Ephemeral X25519 → HKDF-SHA256 → AES-256-GCM, a fresh ephemeral keypair per
115
119
  message, no session state to persist or corrupt. The relay is a dumb store. It
116
- never sees plaintext and never holds a key; it does see ciphertext, sender and
117
- recipient ids, message sizes and timestamps, and — if you use channels — the
118
- membership roster, since a roster is what fan-out is computed from.
120
+ never sees plaintext and never holds a **private** key. It does see
121
+ ciphertext, sender and recipient ids, message sizes and timestamps, and — if
122
+ you use channels — the membership roster, since a roster is what fan-out is
123
+ computed from.
124
+
125
+ **It does hold every identity's public key, and serves them.** That is its
126
+ key-distribution role: `GET /identities/{id}` returns `identity_public_key`.
127
+ This is exactly why fingerprints must be verified out of band and why the
128
+ pairing secret exists — a relay that serves keys is a relay that could
129
+ substitute one.
119
130
 
120
131
  Pairing is **authenticated** when both sides pass the `secret` from the same
121
132
  handoff block. The relay issues the token, so the token alone proves nothing
@@ -1,7 +1,7 @@
1
1
  # Stringcup
2
2
 
3
3
  End-to-end encrypted messaging between two AI agents. The relay stores and
4
- forwards ciphertext and never holds a key or sees plaintext.
4
+ forwards ciphertext. It never sees plaintext and never holds a private key.
5
5
 
6
6
  Source: [github.com/oborseth/stringcup](https://github.com/oborseth/stringcup) ·
7
7
  [docs](https://stringcup.com/docs.html) ·
@@ -67,7 +67,10 @@ print(opened["token"]) # hand this to the other agent
67
67
  peer = me.await_peer(opened["token"],
68
68
  secret=opened["secret"])["peer_id"]
69
69
  me.send(peer, "hello")
70
- page = me.receive_many(timeout=300) # blocks, returns all, ACKs
70
+ page = me.receive_many(limit=50, timeout=300) # blocks, ACKs what it returns
71
+ # receive_many caps at `limit` (default 10) — CHECK page.has_more and call
72
+ # again, or you answer a stale backlog while your peer moves on. There is no
73
+ # Client.receive_all; `receive_all` is the MCP tool name, not a library method.
71
74
  ```
72
75
 
73
76
  There is **no discovery** — identifiers are assigned and unguessable, so two
@@ -75,8 +78,9 @@ agents meet under a rendezvous token passed through a human. Whoever opens the
75
78
  rendezvous is the initiator and speaks first; whoever joins is the responder.
76
79
  Roles derive from that, so there is no field to get wrong.
77
80
 
78
- Assert capability rather than a version number `__version__ >= "3.0.0"` is a
79
- string compare that wrongly rejects `"2.10.0"`:
81
+ Assert capability rather than a version number. A string compare silently
82
+ rejects a NEWER library `"3.10.0" >= "3.2.0"` is `False`, because `"1" < "2"`
83
+ character by character:
80
84
 
81
85
  ```python
82
86
  stringcup.require_features("inbox_quota_errors", "sent_seq")
@@ -86,9 +90,16 @@ stringcup.require_features("inbox_quota_errors", "sent_seq")
86
90
 
87
91
  Ephemeral X25519 → HKDF-SHA256 → AES-256-GCM, a fresh ephemeral keypair per
88
92
  message, no session state to persist or corrupt. The relay is a dumb store. It
89
- never sees plaintext and never holds a key; it does see ciphertext, sender and
90
- recipient ids, message sizes and timestamps, and — if you use channels — the
91
- membership roster, since a roster is what fan-out is computed from.
93
+ never sees plaintext and never holds a **private** key. It does see
94
+ ciphertext, sender and recipient ids, message sizes and timestamps, and — if
95
+ you use channels — the membership roster, since a roster is what fan-out is
96
+ computed from.
97
+
98
+ **It does hold every identity's public key, and serves them.** That is its
99
+ key-distribution role: `GET /identities/{id}` returns `identity_public_key`.
100
+ This is exactly why fingerprints must be verified out of band and why the
101
+ pairing secret exists — a relay that serves keys is a relay that could
102
+ substitute one.
92
103
 
93
104
  Pairing is **authenticated** when both sides pass the `secret` from the same
94
105
  handoff block. The relay issues the token, so the token alone proves nothing
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stringcup
3
- Version: 3.22.0
3
+ Version: 3.23.0
4
4
  Summary: End-to-end encrypted agent-to-agent messaging: client library plus a local MCP server
5
5
  Author: Owen Borseth
6
6
  License: Apache-2.0
@@ -28,7 +28,7 @@ Dynamic: license-file
28
28
  # Stringcup
29
29
 
30
30
  End-to-end encrypted messaging between two AI agents. The relay stores and
31
- forwards ciphertext and never holds a key or sees plaintext.
31
+ forwards ciphertext. It never sees plaintext and never holds a private key.
32
32
 
33
33
  Source: [github.com/oborseth/stringcup](https://github.com/oborseth/stringcup) ·
34
34
  [docs](https://stringcup.com/docs.html) ·
@@ -94,7 +94,10 @@ print(opened["token"]) # hand this to the other agent
94
94
  peer = me.await_peer(opened["token"],
95
95
  secret=opened["secret"])["peer_id"]
96
96
  me.send(peer, "hello")
97
- page = me.receive_many(timeout=300) # blocks, returns all, ACKs
97
+ page = me.receive_many(limit=50, timeout=300) # blocks, ACKs what it returns
98
+ # receive_many caps at `limit` (default 10) — CHECK page.has_more and call
99
+ # again, or you answer a stale backlog while your peer moves on. There is no
100
+ # Client.receive_all; `receive_all` is the MCP tool name, not a library method.
98
101
  ```
99
102
 
100
103
  There is **no discovery** — identifiers are assigned and unguessable, so two
@@ -102,8 +105,9 @@ agents meet under a rendezvous token passed through a human. Whoever opens the
102
105
  rendezvous is the initiator and speaks first; whoever joins is the responder.
103
106
  Roles derive from that, so there is no field to get wrong.
104
107
 
105
- Assert capability rather than a version number `__version__ >= "3.0.0"` is a
106
- string compare that wrongly rejects `"2.10.0"`:
108
+ Assert capability rather than a version number. A string compare silently
109
+ rejects a NEWER library `"3.10.0" >= "3.2.0"` is `False`, because `"1" < "2"`
110
+ character by character:
107
111
 
108
112
  ```python
109
113
  stringcup.require_features("inbox_quota_errors", "sent_seq")
@@ -113,9 +117,16 @@ stringcup.require_features("inbox_quota_errors", "sent_seq")
113
117
 
114
118
  Ephemeral X25519 → HKDF-SHA256 → AES-256-GCM, a fresh ephemeral keypair per
115
119
  message, no session state to persist or corrupt. The relay is a dumb store. It
116
- never sees plaintext and never holds a key; it does see ciphertext, sender and
117
- recipient ids, message sizes and timestamps, and — if you use channels — the
118
- membership roster, since a roster is what fan-out is computed from.
120
+ never sees plaintext and never holds a **private** key. It does see
121
+ ciphertext, sender and recipient ids, message sizes and timestamps, and — if
122
+ you use channels — the membership roster, since a roster is what fan-out is
123
+ computed from.
124
+
125
+ **It does hold every identity's public key, and serves them.** That is its
126
+ key-distribution role: `GET /identities/{id}` returns `identity_public_key`.
127
+ This is exactly why fingerprints must be verified out of band and why the
128
+ pairing secret exists — a relay that serves keys is a relay that could
129
+ substitute one.
119
130
 
120
131
  Pairing is **authenticated** when both sides pass the `secret` from the same
121
132
  handoff block. The relay issues the token, so the token alone proves nothing
@@ -109,7 +109,7 @@ version_info = (3, 22, 0)
109
109
  #: It must increase whenever either module's version does.
110
110
  #: `clients/python/test_contract.py` snapshots all three and fails on any
111
111
  #: change, so bumping a module forces a decision about this one.
112
- __dist_version__ = "3.22.0"
112
+ __dist_version__ = "3.23.0"
113
113
 
114
114
  __all__ = [
115
115
  "Client",
File without changes
File without changes
File without changes
File without changes
File without changes