keep-protocol 0.1.1__tar.gz → 0.3.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.
- keep_protocol-0.3.0/PKG-INFO +216 -0
- keep_protocol-0.3.0/README.md +189 -0
- {keep_protocol-0.1.1 → keep_protocol-0.3.0}/keep/__init__.py +1 -1
- keep_protocol-0.3.0/keep/client.py +378 -0
- keep_protocol-0.3.0/keep_protocol.egg-info/PKG-INFO +216 -0
- {keep_protocol-0.1.1 → keep_protocol-0.3.0}/pyproject.toml +1 -1
- keep_protocol-0.1.1/PKG-INFO +0 -178
- keep_protocol-0.1.1/README.md +0 -151
- keep_protocol-0.1.1/keep/client.py +0 -79
- keep_protocol-0.1.1/keep_protocol.egg-info/PKG-INFO +0 -178
- {keep_protocol-0.1.1 → keep_protocol-0.3.0}/keep/keep_pb2.py +0 -0
- {keep_protocol-0.1.1 → keep_protocol-0.3.0}/keep_protocol.egg-info/SOURCES.txt +0 -0
- {keep_protocol-0.1.1 → keep_protocol-0.3.0}/keep_protocol.egg-info/dependency_links.txt +0 -0
- {keep_protocol-0.1.1 → keep_protocol-0.3.0}/keep_protocol.egg-info/requires.txt +0 -0
- {keep_protocol-0.1.1 → keep_protocol-0.3.0}/keep_protocol.egg-info/top_level.txt +0 -0
- {keep_protocol-0.1.1 → keep_protocol-0.3.0}/setup.cfg +0 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: keep-protocol
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Signed protobuf packets over TCP for AI agent-to-agent communication
|
|
5
|
+
Author: Chris Crawford
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/CLCrawford-dev/keep-protocol
|
|
8
|
+
Project-URL: Repository, https://github.com/CLCrawford-dev/keep-protocol
|
|
9
|
+
Project-URL: Issues, https://github.com/CLCrawford-dev/keep-protocol/issues
|
|
10
|
+
Keywords: agent-protocol,ai-agents,protobuf,ed25519,tcp,agent-communication,mcp,multi-agent
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Communications
|
|
21
|
+
Classifier: Topic :: Security :: Cryptography
|
|
22
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Requires-Dist: protobuf>=4.25
|
|
26
|
+
Requires-Dist: cryptography>=41.0
|
|
27
|
+
|
|
28
|
+
# keep-protocol
|
|
29
|
+
|
|
30
|
+
**Signed Protobuf packets over TCP for AI agent-to-agent communication**
|
|
31
|
+
Claw to claw. Fast. Verifiable. No central authority.
|
|
32
|
+
|
|
33
|
+
> Now available on ClawHub: https://www.clawhub.ai/skills/keep-protocol
|
|
34
|
+
> (Search "keep-protocol" or tags: agent-coordination protobuf tcp ed25519 moltbot openclaw swarm intent)
|
|
35
|
+
|
|
36
|
+
Agents send lightweight `Packet`s to a TCP endpoint (default :9009).
|
|
37
|
+
Unsigned or invalid signatures → **silence** (dropped, no reply).
|
|
38
|
+
Valid ed25519 sig → parsed, logged, replied with `{"body": "done"}`.
|
|
39
|
+
|
|
40
|
+
### Packet (keep.proto)
|
|
41
|
+
|
|
42
|
+
```proto
|
|
43
|
+
message Packet {
|
|
44
|
+
bytes sig = 1; // ed25519 signature (64 bytes)
|
|
45
|
+
bytes pk = 2; // sender's public key (32 bytes)
|
|
46
|
+
uint32 typ = 3; // 0=ask, 1=offer, 2=heartbeat, ...
|
|
47
|
+
string id = 4; // unique ID
|
|
48
|
+
string src = 5; // "bot:my-agent" or "human:chris"
|
|
49
|
+
string dst = 6; // "server", "nearest:weather", "swarm:sailing"
|
|
50
|
+
string body = 7; // intent / payload
|
|
51
|
+
uint64 fee = 8; // micro-fee in satoshis (anti-spam)
|
|
52
|
+
uint32 ttl = 9; // time-to-live seconds
|
|
53
|
+
bytes scar = 10; // gitmem-style memory commit (optional)
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Signature is over serialized bytes without sig/pk (reconstruct & verify).
|
|
58
|
+
|
|
59
|
+
## Quick Start
|
|
60
|
+
|
|
61
|
+
**Run server (Docker, one-liner):**
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
docker run -d -p 9009:9009 --name keep ghcr.io/clcrawford-dev/keep-server:latest
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Wire Format (v0.2.0+)
|
|
68
|
+
|
|
69
|
+
Every message on the wire is length-prefixed:
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
[4 bytes: uint32 big-endian payload length][N bytes: protobuf Packet]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Maximum payload size: 65,536 bytes.
|
|
76
|
+
|
|
77
|
+
**Breaking change from v0.1.x:** Raw protobuf writes are no longer accepted. All clients must use length-prefixed framing.
|
|
78
|
+
|
|
79
|
+
### Python SDK Examples
|
|
80
|
+
|
|
81
|
+
**Install SDK:**
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
pip install keep-protocol
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
**Unsigned send (will be silently dropped):**
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
# Raw unsigned send using generated bindings (requires keep_pb2.py from protoc)
|
|
91
|
+
import socket, struct
|
|
92
|
+
from keep.keep_pb2 import Packet
|
|
93
|
+
|
|
94
|
+
p = Packet(typ=0, id="test-001", src="human:test", dst="server", body="hello claw")
|
|
95
|
+
wire_data = p.SerializeToString()
|
|
96
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
97
|
+
s.connect(("localhost", 9009))
|
|
98
|
+
s.sendall(struct.pack(">I", len(wire_data)) + wire_data)
|
|
99
|
+
# → timeout / silence (unsigned = dropped)
|
|
100
|
+
s.close()
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Signed send (recommended — uses KeepClient):**
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from keep import KeepClient
|
|
107
|
+
|
|
108
|
+
# Auto-generates keypair on first use
|
|
109
|
+
client = KeepClient("localhost", 9009)
|
|
110
|
+
|
|
111
|
+
reply = client.send(
|
|
112
|
+
body="ping from Python",
|
|
113
|
+
src="bot:python-test",
|
|
114
|
+
dst="server",
|
|
115
|
+
fee=1000 # optional anti-spam fee in sats
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
print(reply.body) # → "done"
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Agent-to-Agent Routing (v0.2.0+)
|
|
122
|
+
|
|
123
|
+
Agents register their identity by sending any signed packet — the server maps `src` to the connection. Other agents can then send packets to that identity via `dst`.
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
import threading
|
|
127
|
+
from keep import KeepClient
|
|
128
|
+
|
|
129
|
+
# Agent A: listen for messages
|
|
130
|
+
with KeepClient(src="bot:alice") as alice:
|
|
131
|
+
alice.send(body="register", dst="server", wait_reply=True)
|
|
132
|
+
alice.listen(lambda p: print(f"Got: {p.body}"), timeout=30)
|
|
133
|
+
|
|
134
|
+
# Agent B: send to Alice (in another thread/process)
|
|
135
|
+
with KeepClient(src="bot:bob") as bob:
|
|
136
|
+
bob.send(body="register", dst="server", wait_reply=True)
|
|
137
|
+
bob.send(body="hello alice!", dst="bot:alice")
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
**Routing rules:**
|
|
141
|
+
- `dst="server"` or `dst=""` → server replies `"done"` (backward compatible)
|
|
142
|
+
- `dst="bot:alice"` → forwarded to Alice's connection with original signature intact
|
|
143
|
+
- Destination offline → sender gets `body: "error:offline"`
|
|
144
|
+
- Delivery failure → sender gets `body: "error:delivery_failed"`
|
|
145
|
+
|
|
146
|
+
See `examples/routing_basic.py` for a full working demo.
|
|
147
|
+
|
|
148
|
+
## Discovery (v0.3.0+)
|
|
149
|
+
|
|
150
|
+
Agents can query the server for metadata and discover who's connected using `dst` conventions:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
from keep import KeepClient
|
|
154
|
+
|
|
155
|
+
client = KeepClient("localhost", 9009)
|
|
156
|
+
|
|
157
|
+
# Server info: version, uptime, agent count
|
|
158
|
+
info = client.discover("info")
|
|
159
|
+
# → {"version": "0.3.0", "agents_online": 3, "uptime_sec": 1234}
|
|
160
|
+
|
|
161
|
+
# List connected agents
|
|
162
|
+
agents = client.discover_agents()
|
|
163
|
+
# → ["bot:alice", "bot:weather", "bot:planner"]
|
|
164
|
+
|
|
165
|
+
# Scar exchange stats
|
|
166
|
+
stats = client.discover("stats")
|
|
167
|
+
# → {"scar_exchanges": {"bot:alice": 5}, "total_packets": 42}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
**Discovery conventions:**
|
|
171
|
+
| `dst` value | Response |
|
|
172
|
+
|-------------|----------|
|
|
173
|
+
| `"discover:info"` | Server version, agent count, uptime |
|
|
174
|
+
| `"discover:agents"` | List of connected agent identities |
|
|
175
|
+
| `"discover:stats"` | Scar exchange counts, total packets |
|
|
176
|
+
|
|
177
|
+
**Endpoint caching:** The SDK can cache discovered endpoints in `~/.keep/endpoints.json` for reconnection:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
# Cache after discovery
|
|
181
|
+
KeepClient.cache_endpoint("localhost", 9009, info)
|
|
182
|
+
|
|
183
|
+
# Reconnect from cache (tries each cached endpoint)
|
|
184
|
+
client = KeepClient.from_cache(src="bot:my-agent")
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
See `examples/discovery_basic.py` for a full working demo.
|
|
188
|
+
|
|
189
|
+
## Why Use It?
|
|
190
|
+
|
|
191
|
+
- **Local swarm:** Zero-latency handoff between agents on same machine.
|
|
192
|
+
- **Relay swarm:** Semantic routing via public/private relays (fee + ttl = spam control).
|
|
193
|
+
- **Memory barter:** `scar` field for sharing gitmem commits.
|
|
194
|
+
- **Identity without accounts:** Just a keypair — no registration.
|
|
195
|
+
- **No bloat:** Pure TCP + Protobuf, no HTTP/JSON overhead.
|
|
196
|
+
|
|
197
|
+
## OpenClaw / Moltbot Integration
|
|
198
|
+
|
|
199
|
+
Prompt your agent:
|
|
200
|
+
|
|
201
|
+
```text
|
|
202
|
+
Use keep-protocol to coordinate: send signed Packet to localhost:9009 body 'book sailing trip' src 'bot:me' dst 'swarm:sailing-planner' fee 1000 ttl 300
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Repo:** https://github.com/CLCrawford-dev/keep-protocol
|
|
206
|
+
**Docker:** `ghcr.io/clcrawford-dev/keep-server:latest`
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
**Active development happens here:** https://github.com/CLCrawford-dev/keep-protocol
|
|
211
|
+
Please open issues, PRs, and discussions on the original personal repo.
|
|
212
|
+
This nTEG-dev fork is a public mirror for visibility and ClawHub integration.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
🦀 Keep it signed. Keep it simple. Claw to claw.
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# keep-protocol
|
|
2
|
+
|
|
3
|
+
**Signed Protobuf packets over TCP for AI agent-to-agent communication**
|
|
4
|
+
Claw to claw. Fast. Verifiable. No central authority.
|
|
5
|
+
|
|
6
|
+
> Now available on ClawHub: https://www.clawhub.ai/skills/keep-protocol
|
|
7
|
+
> (Search "keep-protocol" or tags: agent-coordination protobuf tcp ed25519 moltbot openclaw swarm intent)
|
|
8
|
+
|
|
9
|
+
Agents send lightweight `Packet`s to a TCP endpoint (default :9009).
|
|
10
|
+
Unsigned or invalid signatures → **silence** (dropped, no reply).
|
|
11
|
+
Valid ed25519 sig → parsed, logged, replied with `{"body": "done"}`.
|
|
12
|
+
|
|
13
|
+
### Packet (keep.proto)
|
|
14
|
+
|
|
15
|
+
```proto
|
|
16
|
+
message Packet {
|
|
17
|
+
bytes sig = 1; // ed25519 signature (64 bytes)
|
|
18
|
+
bytes pk = 2; // sender's public key (32 bytes)
|
|
19
|
+
uint32 typ = 3; // 0=ask, 1=offer, 2=heartbeat, ...
|
|
20
|
+
string id = 4; // unique ID
|
|
21
|
+
string src = 5; // "bot:my-agent" or "human:chris"
|
|
22
|
+
string dst = 6; // "server", "nearest:weather", "swarm:sailing"
|
|
23
|
+
string body = 7; // intent / payload
|
|
24
|
+
uint64 fee = 8; // micro-fee in satoshis (anti-spam)
|
|
25
|
+
uint32 ttl = 9; // time-to-live seconds
|
|
26
|
+
bytes scar = 10; // gitmem-style memory commit (optional)
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Signature is over serialized bytes without sig/pk (reconstruct & verify).
|
|
31
|
+
|
|
32
|
+
## Quick Start
|
|
33
|
+
|
|
34
|
+
**Run server (Docker, one-liner):**
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
docker run -d -p 9009:9009 --name keep ghcr.io/clcrawford-dev/keep-server:latest
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Wire Format (v0.2.0+)
|
|
41
|
+
|
|
42
|
+
Every message on the wire is length-prefixed:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
[4 bytes: uint32 big-endian payload length][N bytes: protobuf Packet]
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Maximum payload size: 65,536 bytes.
|
|
49
|
+
|
|
50
|
+
**Breaking change from v0.1.x:** Raw protobuf writes are no longer accepted. All clients must use length-prefixed framing.
|
|
51
|
+
|
|
52
|
+
### Python SDK Examples
|
|
53
|
+
|
|
54
|
+
**Install SDK:**
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install keep-protocol
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Unsigned send (will be silently dropped):**
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
# Raw unsigned send using generated bindings (requires keep_pb2.py from protoc)
|
|
64
|
+
import socket, struct
|
|
65
|
+
from keep.keep_pb2 import Packet
|
|
66
|
+
|
|
67
|
+
p = Packet(typ=0, id="test-001", src="human:test", dst="server", body="hello claw")
|
|
68
|
+
wire_data = p.SerializeToString()
|
|
69
|
+
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
70
|
+
s.connect(("localhost", 9009))
|
|
71
|
+
s.sendall(struct.pack(">I", len(wire_data)) + wire_data)
|
|
72
|
+
# → timeout / silence (unsigned = dropped)
|
|
73
|
+
s.close()
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Signed send (recommended — uses KeepClient):**
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from keep import KeepClient
|
|
80
|
+
|
|
81
|
+
# Auto-generates keypair on first use
|
|
82
|
+
client = KeepClient("localhost", 9009)
|
|
83
|
+
|
|
84
|
+
reply = client.send(
|
|
85
|
+
body="ping from Python",
|
|
86
|
+
src="bot:python-test",
|
|
87
|
+
dst="server",
|
|
88
|
+
fee=1000 # optional anti-spam fee in sats
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
print(reply.body) # → "done"
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Agent-to-Agent Routing (v0.2.0+)
|
|
95
|
+
|
|
96
|
+
Agents register their identity by sending any signed packet — the server maps `src` to the connection. Other agents can then send packets to that identity via `dst`.
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
import threading
|
|
100
|
+
from keep import KeepClient
|
|
101
|
+
|
|
102
|
+
# Agent A: listen for messages
|
|
103
|
+
with KeepClient(src="bot:alice") as alice:
|
|
104
|
+
alice.send(body="register", dst="server", wait_reply=True)
|
|
105
|
+
alice.listen(lambda p: print(f"Got: {p.body}"), timeout=30)
|
|
106
|
+
|
|
107
|
+
# Agent B: send to Alice (in another thread/process)
|
|
108
|
+
with KeepClient(src="bot:bob") as bob:
|
|
109
|
+
bob.send(body="register", dst="server", wait_reply=True)
|
|
110
|
+
bob.send(body="hello alice!", dst="bot:alice")
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Routing rules:**
|
|
114
|
+
- `dst="server"` or `dst=""` → server replies `"done"` (backward compatible)
|
|
115
|
+
- `dst="bot:alice"` → forwarded to Alice's connection with original signature intact
|
|
116
|
+
- Destination offline → sender gets `body: "error:offline"`
|
|
117
|
+
- Delivery failure → sender gets `body: "error:delivery_failed"`
|
|
118
|
+
|
|
119
|
+
See `examples/routing_basic.py` for a full working demo.
|
|
120
|
+
|
|
121
|
+
## Discovery (v0.3.0+)
|
|
122
|
+
|
|
123
|
+
Agents can query the server for metadata and discover who's connected using `dst` conventions:
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from keep import KeepClient
|
|
127
|
+
|
|
128
|
+
client = KeepClient("localhost", 9009)
|
|
129
|
+
|
|
130
|
+
# Server info: version, uptime, agent count
|
|
131
|
+
info = client.discover("info")
|
|
132
|
+
# → {"version": "0.3.0", "agents_online": 3, "uptime_sec": 1234}
|
|
133
|
+
|
|
134
|
+
# List connected agents
|
|
135
|
+
agents = client.discover_agents()
|
|
136
|
+
# → ["bot:alice", "bot:weather", "bot:planner"]
|
|
137
|
+
|
|
138
|
+
# Scar exchange stats
|
|
139
|
+
stats = client.discover("stats")
|
|
140
|
+
# → {"scar_exchanges": {"bot:alice": 5}, "total_packets": 42}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Discovery conventions:**
|
|
144
|
+
| `dst` value | Response |
|
|
145
|
+
|-------------|----------|
|
|
146
|
+
| `"discover:info"` | Server version, agent count, uptime |
|
|
147
|
+
| `"discover:agents"` | List of connected agent identities |
|
|
148
|
+
| `"discover:stats"` | Scar exchange counts, total packets |
|
|
149
|
+
|
|
150
|
+
**Endpoint caching:** The SDK can cache discovered endpoints in `~/.keep/endpoints.json` for reconnection:
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
# Cache after discovery
|
|
154
|
+
KeepClient.cache_endpoint("localhost", 9009, info)
|
|
155
|
+
|
|
156
|
+
# Reconnect from cache (tries each cached endpoint)
|
|
157
|
+
client = KeepClient.from_cache(src="bot:my-agent")
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
See `examples/discovery_basic.py` for a full working demo.
|
|
161
|
+
|
|
162
|
+
## Why Use It?
|
|
163
|
+
|
|
164
|
+
- **Local swarm:** Zero-latency handoff between agents on same machine.
|
|
165
|
+
- **Relay swarm:** Semantic routing via public/private relays (fee + ttl = spam control).
|
|
166
|
+
- **Memory barter:** `scar` field for sharing gitmem commits.
|
|
167
|
+
- **Identity without accounts:** Just a keypair — no registration.
|
|
168
|
+
- **No bloat:** Pure TCP + Protobuf, no HTTP/JSON overhead.
|
|
169
|
+
|
|
170
|
+
## OpenClaw / Moltbot Integration
|
|
171
|
+
|
|
172
|
+
Prompt your agent:
|
|
173
|
+
|
|
174
|
+
```text
|
|
175
|
+
Use keep-protocol to coordinate: send signed Packet to localhost:9009 body 'book sailing trip' src 'bot:me' dst 'swarm:sailing-planner' fee 1000 ttl 300
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
**Repo:** https://github.com/CLCrawford-dev/keep-protocol
|
|
179
|
+
**Docker:** `ghcr.io/clcrawford-dev/keep-server:latest`
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
**Active development happens here:** https://github.com/CLCrawford-dev/keep-protocol
|
|
184
|
+
Please open issues, PRs, and discussions on the original personal repo.
|
|
185
|
+
This nTEG-dev fork is a public mirror for visibility and ClawHub integration.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
🦀 Keep it signed. Keep it simple. Claw to claw.
|