clowl 0.2.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.
- clowl-0.2.0/PKG-INFO +196 -0
- clowl-0.2.0/README.md +172 -0
- clowl-0.2.0/clowl/__init__.py +46 -0
- clowl-0.2.0/clowl/clowl.py +493 -0
- clowl-0.2.0/clowl.egg-info/PKG-INFO +196 -0
- clowl-0.2.0/clowl.egg-info/SOURCES.txt +8 -0
- clowl-0.2.0/clowl.egg-info/dependency_links.txt +1 -0
- clowl-0.2.0/clowl.egg-info/top_level.txt +2 -0
- clowl-0.2.0/pyproject.toml +37 -0
- clowl-0.2.0/setup.cfg +4 -0
clowl-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clowl
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: CLowl - A structured communication language for AI agent-to-agent messaging
|
|
5
|
+
Author-email: Oscar Sterling Agency <oscar@oscarsterling.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://clowl.dev
|
|
8
|
+
Project-URL: Repository, https://github.com/oscarsterling/clowl
|
|
9
|
+
Project-URL: Documentation, https://clowl.dev
|
|
10
|
+
Keywords: clowl,agent,ai,protocol,communication,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.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.8
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# CLowl
|
|
26
|
+
|
|
27
|
+
**A language for AI agents that humans can read.**
|
|
28
|
+
|
|
29
|
+
CLowl (Claw + Talk) is a structured communication protocol for AI agent-to-agent messaging. It defines a minimal JSON schema with typed performatives, message metadata, and context referencing that runs on top of any transport (MCP, A2A, HTTP, WebSocket, stdio, etc.). Every CLowl message has a deterministic English translation, so enterprises can audit any agent conversation and developers can debug multi-agent pipelines in seconds.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
**TypeScript / Node.js:**
|
|
36
|
+
```bash
|
|
37
|
+
npm install clowl-protocol
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Python:**
|
|
41
|
+
```bash
|
|
42
|
+
pip install clowl
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
**TypeScript:**
|
|
50
|
+
```typescript
|
|
51
|
+
import { createReq, generateCid, generateTid } from "clowl";
|
|
52
|
+
|
|
53
|
+
const cid = generateCid();
|
|
54
|
+
const req = createReq("oscar", "radar", cid, "search", { q: "MCP vs A2A" }, { tid: generateTid() });
|
|
55
|
+
console.log(req.toHuman());
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Python:**
|
|
59
|
+
```python
|
|
60
|
+
from clowl import create_req, generate_cid, generate_tid
|
|
61
|
+
|
|
62
|
+
cid = generate_cid()
|
|
63
|
+
req = create_req("oscar", "radar", cid, "search", {"q": "MCP vs A2A"}, tid=generate_tid())
|
|
64
|
+
print(req.to_human())
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## The Problem
|
|
70
|
+
|
|
71
|
+
When AI agents talk to each other, nobody knows what they're saying.
|
|
72
|
+
|
|
73
|
+
Agent A sends a blob of unstructured text to Agent B. Agent B replies with another blob. Somewhere in that chain, something goes wrong, and you have no idea what was requested, what was delegated, or where the task died. Multi-agent systems are powerful and opaque in equal measure.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## The Solution
|
|
78
|
+
|
|
79
|
+
CLowl is a language agents speak and humans can read. It defines:
|
|
80
|
+
|
|
81
|
+
- **Structured messages** with typed intent (requests, delegates, errors, progress updates)
|
|
82
|
+
- **Message metadata** for tracing, dedup, and conversation reconstruction
|
|
83
|
+
- **A live translator** that converts CLowl JSON to plain English instantly
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Message Format
|
|
88
|
+
|
|
89
|
+
Every CLowl message is a JSON object with 8 required fields:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"clowl": "0.2",
|
|
94
|
+
"mid": "01914b2c-7abc-...",
|
|
95
|
+
"ts": 1709078400,
|
|
96
|
+
"p": "REQ",
|
|
97
|
+
"from": "oscar",
|
|
98
|
+
"to": "radar",
|
|
99
|
+
"cid": "conv-001",
|
|
100
|
+
"body": {
|
|
101
|
+
"t": "search",
|
|
102
|
+
"d": { "q": "MCP vs A2A", "scope": "web" }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Optional fields: `tid` (trace ID), `pid` (parent message), `ctx` (context reference), `auth` (auth token), `det` (determinism flag).
|
|
108
|
+
|
|
109
|
+
See the [full spec](spec-v0.2.md) for complete field definitions.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Performatives
|
|
114
|
+
|
|
115
|
+
| Code | Name | Meaning |
|
|
116
|
+
|------|------|---------|
|
|
117
|
+
| `REQ` | Request | "Do this thing." Initiates a task. |
|
|
118
|
+
| `INF` | Inform | "Here is information." No action expected. |
|
|
119
|
+
| `ACK` | Acknowledge | "Got it, proceeding." |
|
|
120
|
+
| `ERR` | Error | "Failed. Here's why." Structured error code + message. |
|
|
121
|
+
| `DLGT` | Delegate | "Passing to someone better suited." Requires `delegation_mode`: `transfer`, `fork`, or `assist`. |
|
|
122
|
+
| `DONE` | Complete | "Finished. Here's the result." |
|
|
123
|
+
| `CNCL` | Cancel | "Abort this task." |
|
|
124
|
+
| `QRY` | Query | "What's the status?" or "Give me info without acting." |
|
|
125
|
+
| `PROG` | Progress | "Here's an update on the running task." |
|
|
126
|
+
| `CAPS` | Capabilities | "Here's what I can do." Broadcast on connection. |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## The Translator
|
|
131
|
+
|
|
132
|
+
Paste CLowl JSON, get English. Paste English, get CLowl JSON. No API needed.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
$ python translator.py '{"clowl":"0.2","mid":"m001","ts":1709078400,"tid":"t001","p":"REQ","from":"oscar","to":"radar","cid":"c001","body":{"t":"search","d":{"q":"CLowl competitors","scope":"web"}}}'
|
|
136
|
+
|
|
137
|
+
[2026-02-27 12:00:00 UTC] [t001] [m001...] oscar > radar: REQUEST search
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Integration Options
|
|
143
|
+
|
|
144
|
+
**Option 1: Inject the system prompt**
|
|
145
|
+
|
|
146
|
+
Add [`system-prompt-v0.2.md`](system-prompt-v0.2.md) to your agent's system prompt. Any LLM will start generating CLowl messages immediately.
|
|
147
|
+
|
|
148
|
+
**Option 2: Add the JSON schema to your tool calls**
|
|
149
|
+
|
|
150
|
+
Use [`clowl-schema.json`](clowl-schema.json) as a function-calling tool definition. Works with OpenAI, Anthropic, Google, and any local model that supports structured output.
|
|
151
|
+
|
|
152
|
+
**Option 3: Use the libraries**
|
|
153
|
+
|
|
154
|
+
TypeScript and Python libraries provide message creation, validation, state tracking, and translation with zero external dependencies.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Examples
|
|
159
|
+
|
|
160
|
+
See [`examples-v0.2.md`](examples-v0.2.md) for full examples with CLowl JSON and English translations.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Roadmap
|
|
165
|
+
|
|
166
|
+
**v0.3 (planned)**
|
|
167
|
+
- Three-layer architecture (Semantic / Coordination / Transport)
|
|
168
|
+
- Streaming progress (chunked PROG messages)
|
|
169
|
+
- Cryptographic message signing
|
|
170
|
+
- Go SDK
|
|
171
|
+
|
|
172
|
+
**v1.0 (stable)**
|
|
173
|
+
- Breaking changes locked out
|
|
174
|
+
- Binary encoding option (MessagePack)
|
|
175
|
+
- Production SDK with retry, dedup, and dead-letter queue support
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Contributing
|
|
180
|
+
|
|
181
|
+
CLowl is an open spec. Contributions welcome:
|
|
182
|
+
|
|
183
|
+
1. Fork the repo
|
|
184
|
+
2. Read [`spec-v0.2.md`](spec-v0.2.md) for the source of truth
|
|
185
|
+
3. Open an issue for design questions before building
|
|
186
|
+
4. PRs should include spec changes + updated examples
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
Built by [Oscar Sterling Agency](https://clowl.dev) | [clowl.dev](https://clowl.dev)
|
clowl-0.2.0/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# CLowl
|
|
2
|
+
|
|
3
|
+
**A language for AI agents that humans can read.**
|
|
4
|
+
|
|
5
|
+
CLowl (Claw + Talk) is a structured communication protocol for AI agent-to-agent messaging. It defines a minimal JSON schema with typed performatives, message metadata, and context referencing that runs on top of any transport (MCP, A2A, HTTP, WebSocket, stdio, etc.). Every CLowl message has a deterministic English translation, so enterprises can audit any agent conversation and developers can debug multi-agent pipelines in seconds.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
**TypeScript / Node.js:**
|
|
12
|
+
```bash
|
|
13
|
+
npm install clowl-protocol
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
**Python:**
|
|
17
|
+
```bash
|
|
18
|
+
pip install clowl
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
24
|
+
|
|
25
|
+
**TypeScript:**
|
|
26
|
+
```typescript
|
|
27
|
+
import { createReq, generateCid, generateTid } from "clowl";
|
|
28
|
+
|
|
29
|
+
const cid = generateCid();
|
|
30
|
+
const req = createReq("oscar", "radar", cid, "search", { q: "MCP vs A2A" }, { tid: generateTid() });
|
|
31
|
+
console.log(req.toHuman());
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Python:**
|
|
35
|
+
```python
|
|
36
|
+
from clowl import create_req, generate_cid, generate_tid
|
|
37
|
+
|
|
38
|
+
cid = generate_cid()
|
|
39
|
+
req = create_req("oscar", "radar", cid, "search", {"q": "MCP vs A2A"}, tid=generate_tid())
|
|
40
|
+
print(req.to_human())
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## The Problem
|
|
46
|
+
|
|
47
|
+
When AI agents talk to each other, nobody knows what they're saying.
|
|
48
|
+
|
|
49
|
+
Agent A sends a blob of unstructured text to Agent B. Agent B replies with another blob. Somewhere in that chain, something goes wrong, and you have no idea what was requested, what was delegated, or where the task died. Multi-agent systems are powerful and opaque in equal measure.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## The Solution
|
|
54
|
+
|
|
55
|
+
CLowl is a language agents speak and humans can read. It defines:
|
|
56
|
+
|
|
57
|
+
- **Structured messages** with typed intent (requests, delegates, errors, progress updates)
|
|
58
|
+
- **Message metadata** for tracing, dedup, and conversation reconstruction
|
|
59
|
+
- **A live translator** that converts CLowl JSON to plain English instantly
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Message Format
|
|
64
|
+
|
|
65
|
+
Every CLowl message is a JSON object with 8 required fields:
|
|
66
|
+
|
|
67
|
+
```json
|
|
68
|
+
{
|
|
69
|
+
"clowl": "0.2",
|
|
70
|
+
"mid": "01914b2c-7abc-...",
|
|
71
|
+
"ts": 1709078400,
|
|
72
|
+
"p": "REQ",
|
|
73
|
+
"from": "oscar",
|
|
74
|
+
"to": "radar",
|
|
75
|
+
"cid": "conv-001",
|
|
76
|
+
"body": {
|
|
77
|
+
"t": "search",
|
|
78
|
+
"d": { "q": "MCP vs A2A", "scope": "web" }
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Optional fields: `tid` (trace ID), `pid` (parent message), `ctx` (context reference), `auth` (auth token), `det` (determinism flag).
|
|
84
|
+
|
|
85
|
+
See the [full spec](spec-v0.2.md) for complete field definitions.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
## Performatives
|
|
90
|
+
|
|
91
|
+
| Code | Name | Meaning |
|
|
92
|
+
|------|------|---------|
|
|
93
|
+
| `REQ` | Request | "Do this thing." Initiates a task. |
|
|
94
|
+
| `INF` | Inform | "Here is information." No action expected. |
|
|
95
|
+
| `ACK` | Acknowledge | "Got it, proceeding." |
|
|
96
|
+
| `ERR` | Error | "Failed. Here's why." Structured error code + message. |
|
|
97
|
+
| `DLGT` | Delegate | "Passing to someone better suited." Requires `delegation_mode`: `transfer`, `fork`, or `assist`. |
|
|
98
|
+
| `DONE` | Complete | "Finished. Here's the result." |
|
|
99
|
+
| `CNCL` | Cancel | "Abort this task." |
|
|
100
|
+
| `QRY` | Query | "What's the status?" or "Give me info without acting." |
|
|
101
|
+
| `PROG` | Progress | "Here's an update on the running task." |
|
|
102
|
+
| `CAPS` | Capabilities | "Here's what I can do." Broadcast on connection. |
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## The Translator
|
|
107
|
+
|
|
108
|
+
Paste CLowl JSON, get English. Paste English, get CLowl JSON. No API needed.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
$ python translator.py '{"clowl":"0.2","mid":"m001","ts":1709078400,"tid":"t001","p":"REQ","from":"oscar","to":"radar","cid":"c001","body":{"t":"search","d":{"q":"CLowl competitors","scope":"web"}}}'
|
|
112
|
+
|
|
113
|
+
[2026-02-27 12:00:00 UTC] [t001] [m001...] oscar > radar: REQUEST search
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Integration Options
|
|
119
|
+
|
|
120
|
+
**Option 1: Inject the system prompt**
|
|
121
|
+
|
|
122
|
+
Add [`system-prompt-v0.2.md`](system-prompt-v0.2.md) to your agent's system prompt. Any LLM will start generating CLowl messages immediately.
|
|
123
|
+
|
|
124
|
+
**Option 2: Add the JSON schema to your tool calls**
|
|
125
|
+
|
|
126
|
+
Use [`clowl-schema.json`](clowl-schema.json) as a function-calling tool definition. Works with OpenAI, Anthropic, Google, and any local model that supports structured output.
|
|
127
|
+
|
|
128
|
+
**Option 3: Use the libraries**
|
|
129
|
+
|
|
130
|
+
TypeScript and Python libraries provide message creation, validation, state tracking, and translation with zero external dependencies.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## Examples
|
|
135
|
+
|
|
136
|
+
See [`examples-v0.2.md`](examples-v0.2.md) for full examples with CLowl JSON and English translations.
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Roadmap
|
|
141
|
+
|
|
142
|
+
**v0.3 (planned)**
|
|
143
|
+
- Three-layer architecture (Semantic / Coordination / Transport)
|
|
144
|
+
- Streaming progress (chunked PROG messages)
|
|
145
|
+
- Cryptographic message signing
|
|
146
|
+
- Go SDK
|
|
147
|
+
|
|
148
|
+
**v1.0 (stable)**
|
|
149
|
+
- Breaking changes locked out
|
|
150
|
+
- Binary encoding option (MessagePack)
|
|
151
|
+
- Production SDK with retry, dedup, and dead-letter queue support
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Contributing
|
|
156
|
+
|
|
157
|
+
CLowl is an open spec. Contributions welcome:
|
|
158
|
+
|
|
159
|
+
1. Fork the repo
|
|
160
|
+
2. Read [`spec-v0.2.md`](spec-v0.2.md) for the source of truth
|
|
161
|
+
3. Open an issue for design questions before building
|
|
162
|
+
4. PRs should include spec changes + updated examples
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## License
|
|
167
|
+
|
|
168
|
+
MIT
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
Built by [Oscar Sterling Agency](https://clowl.dev) | [clowl.dev](https://clowl.dev)
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""CLowl v0.2 - A structured communication language for AI agent-to-agent messaging.
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
from clowl import CLowlMessage, create_req, create_done, generate_cid, generate_tid
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .clowl import (
|
|
8
|
+
CLOWL_VERSION,
|
|
9
|
+
VALID_PERFORMATIVES,
|
|
10
|
+
VALID_DELEGATION_MODES,
|
|
11
|
+
PERFORMATIVE_NAMES,
|
|
12
|
+
CLowlMessage,
|
|
13
|
+
generate_mid,
|
|
14
|
+
generate_cid,
|
|
15
|
+
generate_tid,
|
|
16
|
+
sha256_of_file,
|
|
17
|
+
create_req,
|
|
18
|
+
create_ack,
|
|
19
|
+
create_done,
|
|
20
|
+
create_err,
|
|
21
|
+
create_dlgt,
|
|
22
|
+
create_prog,
|
|
23
|
+
create_caps,
|
|
24
|
+
create_cncl,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
__version__ = CLOWL_VERSION
|
|
28
|
+
__all__ = [
|
|
29
|
+
"CLOWL_VERSION",
|
|
30
|
+
"VALID_PERFORMATIVES",
|
|
31
|
+
"VALID_DELEGATION_MODES",
|
|
32
|
+
"PERFORMATIVE_NAMES",
|
|
33
|
+
"CLowlMessage",
|
|
34
|
+
"generate_mid",
|
|
35
|
+
"generate_cid",
|
|
36
|
+
"generate_tid",
|
|
37
|
+
"sha256_of_file",
|
|
38
|
+
"create_req",
|
|
39
|
+
"create_ack",
|
|
40
|
+
"create_done",
|
|
41
|
+
"create_err",
|
|
42
|
+
"create_dlgt",
|
|
43
|
+
"create_prog",
|
|
44
|
+
"create_caps",
|
|
45
|
+
"create_cncl",
|
|
46
|
+
]
|
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""CLowl v0.2 — Reference Python Library
|
|
3
|
+
|
|
4
|
+
A minimal, stdlib-only Python library for creating, validating, and translating
|
|
5
|
+
CLowl messages.
|
|
6
|
+
|
|
7
|
+
No external dependencies. Python 3.8+ only.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import json
|
|
11
|
+
import time
|
|
12
|
+
import uuid
|
|
13
|
+
import hashlib
|
|
14
|
+
from datetime import datetime, timezone
|
|
15
|
+
from typing import Optional, Union, List
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# ---------------------------------------------------------------------------
|
|
19
|
+
# Constants
|
|
20
|
+
# ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
CLOWL_VERSION = "0.2"
|
|
23
|
+
|
|
24
|
+
VALID_PERFORMATIVES = frozenset({
|
|
25
|
+
"REQ", "INF", "ACK", "ERR", "DLGT", "DONE", "CNCL", "QRY", "PROG", "CAPS"
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
VALID_DELEGATION_MODES = frozenset({"transfer", "fork", "assist"})
|
|
29
|
+
|
|
30
|
+
PERFORMATIVE_NAMES = {
|
|
31
|
+
"REQ": "REQUEST",
|
|
32
|
+
"INF": "INFORM",
|
|
33
|
+
"ACK": "ACKNOWLEDGE",
|
|
34
|
+
"ERR": "ERROR",
|
|
35
|
+
"DLGT": "DELEGATE",
|
|
36
|
+
"DONE": "COMPLETE",
|
|
37
|
+
"CNCL": "CANCEL",
|
|
38
|
+
"QRY": "QUERY",
|
|
39
|
+
"PROG": "PROGRESS",
|
|
40
|
+
"CAPS": "CAPABILITIES",
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# ---------------------------------------------------------------------------
|
|
45
|
+
# ID generation
|
|
46
|
+
# ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
def generate_mid() -> str:
|
|
49
|
+
"""Generate a time-ordered message ID (UUIDv7-style).
|
|
50
|
+
|
|
51
|
+
Format: <timestamp_ms_hex>-<4-hex-groups from uuid4>
|
|
52
|
+
Sorts lexicographically by creation time.
|
|
53
|
+
"""
|
|
54
|
+
ts_ms = int(time.time() * 1000)
|
|
55
|
+
rand = uuid.uuid4().hex
|
|
56
|
+
# UUIDv7-style: 48-bit timestamp prefix + version + random
|
|
57
|
+
return f"{ts_ms:012x}-7{rand[1:4]}-{rand[4:8]}-{rand[8:12]}-{rand[12:24]}"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def generate_cid() -> str:
|
|
61
|
+
"""Generate a conversation ID."""
|
|
62
|
+
return uuid.uuid4().hex[:16]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def generate_tid() -> str:
|
|
66
|
+
"""Generate a trace ID."""
|
|
67
|
+
return "t-" + uuid.uuid4().hex[:12]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def sha256_of_file(path: str) -> Optional[str]:
|
|
71
|
+
"""Compute SHA-256 of a file's contents. Returns None if file not found."""
|
|
72
|
+
try:
|
|
73
|
+
with open(path, "rb") as f:
|
|
74
|
+
return hashlib.sha256(f.read()).hexdigest()
|
|
75
|
+
except (FileNotFoundError, PermissionError):
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# ---------------------------------------------------------------------------
|
|
80
|
+
# CLowlMessage
|
|
81
|
+
# ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
class CLowlMessage:
|
|
84
|
+
"""Represents a single CLowl v0.2 message.
|
|
85
|
+
|
|
86
|
+
Required fields: clowl, mid, ts, p, from_, to, cid, body
|
|
87
|
+
Optional fields: tid, pid, ctx, auth, det
|
|
88
|
+
"""
|
|
89
|
+
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
p: str,
|
|
93
|
+
from_: str,
|
|
94
|
+
to: Union[str, List[str]],
|
|
95
|
+
cid: str,
|
|
96
|
+
body_t: str,
|
|
97
|
+
body_d: Optional[dict] = None,
|
|
98
|
+
*,
|
|
99
|
+
mid: Optional[str] = None,
|
|
100
|
+
ts: Optional[int] = None,
|
|
101
|
+
tid: Optional[str] = None,
|
|
102
|
+
pid: Optional[str] = None,
|
|
103
|
+
ctx: Optional[dict] = None,
|
|
104
|
+
auth: Optional[str] = None,
|
|
105
|
+
det: bool = False,
|
|
106
|
+
):
|
|
107
|
+
self.clowl = CLOWL_VERSION
|
|
108
|
+
self.mid = mid or generate_mid()
|
|
109
|
+
self.ts = ts or int(time.time())
|
|
110
|
+
self.tid = tid
|
|
111
|
+
self.pid = pid
|
|
112
|
+
self.p = p
|
|
113
|
+
self.from_ = from_
|
|
114
|
+
self.to = to
|
|
115
|
+
self.cid = cid
|
|
116
|
+
self.body = {"t": body_t, "d": body_d or {}}
|
|
117
|
+
self.ctx = ctx
|
|
118
|
+
self.auth = auth
|
|
119
|
+
self.det = det
|
|
120
|
+
|
|
121
|
+
# ------------------------------------------------------------------
|
|
122
|
+
# Serialization
|
|
123
|
+
# ------------------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
def to_dict(self) -> dict:
|
|
126
|
+
"""Convert to a Python dict (suitable for JSON serialization)."""
|
|
127
|
+
d: dict = {
|
|
128
|
+
"clowl": self.clowl,
|
|
129
|
+
"mid": self.mid,
|
|
130
|
+
"ts": self.ts,
|
|
131
|
+
"p": self.p,
|
|
132
|
+
"from": self.from_,
|
|
133
|
+
"to": self.to,
|
|
134
|
+
"cid": self.cid,
|
|
135
|
+
"body": self.body,
|
|
136
|
+
}
|
|
137
|
+
# Optional fields — only include if set
|
|
138
|
+
if self.tid is not None: d["tid"] = self.tid
|
|
139
|
+
if self.pid is not None: d["pid"] = self.pid
|
|
140
|
+
if self.ctx is not None: d["ctx"] = self.ctx
|
|
141
|
+
if self.auth is not None: d["auth"] = self.auth
|
|
142
|
+
if self.det: d["det"] = self.det
|
|
143
|
+
return d
|
|
144
|
+
|
|
145
|
+
def to_json(self, indent: int = 2) -> str:
|
|
146
|
+
"""Serialize to JSON string."""
|
|
147
|
+
return json.dumps(self.to_dict(), indent=indent)
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def from_dict(cls, data: dict) -> "CLowlMessage":
|
|
151
|
+
"""Construct a CLowlMessage from a dict (e.g., parsed JSON)."""
|
|
152
|
+
body = data.get("body", {})
|
|
153
|
+
body_t = body.get("t", "")
|
|
154
|
+
body_d = body.get("d", {})
|
|
155
|
+
return cls(
|
|
156
|
+
p = data["p"],
|
|
157
|
+
from_ = data["from"],
|
|
158
|
+
to = data["to"],
|
|
159
|
+
cid = data["cid"],
|
|
160
|
+
body_t = body_t,
|
|
161
|
+
body_d = body_d,
|
|
162
|
+
mid = data.get("mid"),
|
|
163
|
+
ts = data.get("ts"),
|
|
164
|
+
tid = data.get("tid"),
|
|
165
|
+
pid = data.get("pid"),
|
|
166
|
+
ctx = data.get("ctx"),
|
|
167
|
+
auth = data.get("auth"),
|
|
168
|
+
det = data.get("det", False),
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
@classmethod
|
|
172
|
+
def from_json(cls, json_str: str) -> "CLowlMessage":
|
|
173
|
+
"""Construct a CLowlMessage from a JSON string."""
|
|
174
|
+
return cls.from_dict(json.loads(json_str))
|
|
175
|
+
|
|
176
|
+
# ------------------------------------------------------------------
|
|
177
|
+
# Validation
|
|
178
|
+
# ------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
def validate(self) -> list:
|
|
181
|
+
"""Validate the message. Returns a list of error strings (empty = valid)."""
|
|
182
|
+
errors = []
|
|
183
|
+
|
|
184
|
+
if self.clowl != CLOWL_VERSION:
|
|
185
|
+
errors.append(f"Invalid clowl version: '{self.clowl}' (expected '{CLOWL_VERSION}')")
|
|
186
|
+
|
|
187
|
+
if not self.mid:
|
|
188
|
+
errors.append("Missing required field: mid")
|
|
189
|
+
|
|
190
|
+
if not isinstance(self.ts, int) or self.ts < 0:
|
|
191
|
+
errors.append(f"Invalid ts: {self.ts!r} (must be non-negative integer)")
|
|
192
|
+
|
|
193
|
+
if self.p not in VALID_PERFORMATIVES:
|
|
194
|
+
errors.append(f"Invalid performative: '{self.p}' (must be one of {sorted(VALID_PERFORMATIVES)})")
|
|
195
|
+
|
|
196
|
+
if not self.from_:
|
|
197
|
+
errors.append("Missing required field: from")
|
|
198
|
+
|
|
199
|
+
if not self.to:
|
|
200
|
+
errors.append("Missing required field: to")
|
|
201
|
+
|
|
202
|
+
if not self.cid:
|
|
203
|
+
errors.append("Missing required field: cid")
|
|
204
|
+
|
|
205
|
+
if not isinstance(self.body, dict):
|
|
206
|
+
errors.append("body must be an object")
|
|
207
|
+
else:
|
|
208
|
+
if not self.body.get("t"):
|
|
209
|
+
errors.append("body.t is required")
|
|
210
|
+
if not isinstance(self.body.get("d"), dict):
|
|
211
|
+
errors.append("body.d must be an object")
|
|
212
|
+
|
|
213
|
+
# DLGT requires delegation_mode
|
|
214
|
+
if self.p == "DLGT":
|
|
215
|
+
d = self.body.get("d", {})
|
|
216
|
+
mode = d.get("delegation_mode")
|
|
217
|
+
if mode not in VALID_DELEGATION_MODES:
|
|
218
|
+
errors.append(
|
|
219
|
+
f"DLGT messages require body.d.delegation_mode to be one of "
|
|
220
|
+
f"{sorted(VALID_DELEGATION_MODES)}, got {mode!r}"
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# ctx validation
|
|
224
|
+
if self.ctx is not None:
|
|
225
|
+
if not isinstance(self.ctx, dict):
|
|
226
|
+
errors.append("ctx must be an object with ref/inline/hash fields")
|
|
227
|
+
else:
|
|
228
|
+
inline = self.ctx.get("inline")
|
|
229
|
+
if inline and len(inline) > 2000:
|
|
230
|
+
errors.append(f"ctx.inline exceeds 2000 character limit (got {len(inline)})")
|
|
231
|
+
h = self.ctx.get("hash")
|
|
232
|
+
if h and not (isinstance(h, str) and len(h) == 64):
|
|
233
|
+
errors.append(f"ctx.hash must be a 64-char SHA-256 hex string")
|
|
234
|
+
|
|
235
|
+
return errors
|
|
236
|
+
|
|
237
|
+
def is_valid(self) -> bool:
|
|
238
|
+
"""Return True if the message passes validation."""
|
|
239
|
+
return len(self.validate()) == 0
|
|
240
|
+
|
|
241
|
+
# ------------------------------------------------------------------
|
|
242
|
+
# Human translation
|
|
243
|
+
# ------------------------------------------------------------------
|
|
244
|
+
|
|
245
|
+
def to_human(self) -> str:
|
|
246
|
+
"""Return a human-readable English description of this message."""
|
|
247
|
+
ts_str = datetime.fromtimestamp(self.ts, tz=timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")
|
|
248
|
+
perf = PERFORMATIVE_NAMES.get(self.p, self.p)
|
|
249
|
+
to_str = "[" + ", ".join(self.to) + "]" if isinstance(self.to, list) else self.to
|
|
250
|
+
|
|
251
|
+
body_d = self.body.get("d", {})
|
|
252
|
+
body_t = self.body.get("t", "")
|
|
253
|
+
tid_tag = f"[{self.tid}] " if self.tid else ""
|
|
254
|
+
pid_tag = f" (re: {self.pid[:8]}…)" if self.pid else ""
|
|
255
|
+
det_tag = " [deterministic]" if self.det else ""
|
|
256
|
+
auth_tag = " [authenticated]" if self.auth else ""
|
|
257
|
+
|
|
258
|
+
# Body summary
|
|
259
|
+
if self.p == "ERR":
|
|
260
|
+
code = body_d.get("code", "?")
|
|
261
|
+
msg = body_d.get("msg", "unknown error")
|
|
262
|
+
retry = " — retryable" if body_d.get("retry") else ""
|
|
263
|
+
detail = f"[{code}] {msg}{retry}"
|
|
264
|
+
elif self.p == "CAPS":
|
|
265
|
+
supports = body_d.get("supports", [])
|
|
266
|
+
ver = body_d.get("clowl", "?")
|
|
267
|
+
detail = f"CLowl v{ver} | supports: {', '.join(supports)}"
|
|
268
|
+
elif self.p == "DLGT":
|
|
269
|
+
mode = body_d.get("delegation_mode", "transfer")
|
|
270
|
+
rest = {k: v for k, v in body_d.items() if k != "delegation_mode"}
|
|
271
|
+
detail = f"{body_t} [{mode}]"
|
|
272
|
+
if rest:
|
|
273
|
+
detail += f" — {json.dumps(rest)}"
|
|
274
|
+
elif self.p == "PROG":
|
|
275
|
+
pct = body_d.get("pct")
|
|
276
|
+
note = body_d.get("note", "")
|
|
277
|
+
detail = f"{body_t} — {pct}% {note}".strip(" —") if pct is not None else f"{body_t} — {note}"
|
|
278
|
+
elif self.p == "ACK":
|
|
279
|
+
eta = body_d.get("eta")
|
|
280
|
+
detail = f"Acknowledged {body_t}" + (f" — ETA {eta}" if eta else "")
|
|
281
|
+
elif body_d:
|
|
282
|
+
detail = f"{body_t} — {json.dumps(body_d)}"
|
|
283
|
+
else:
|
|
284
|
+
detail = body_t
|
|
285
|
+
|
|
286
|
+
ctx_str = ""
|
|
287
|
+
if self.ctx:
|
|
288
|
+
ref = self.ctx.get("ref")
|
|
289
|
+
h = self.ctx.get("hash")
|
|
290
|
+
if ref:
|
|
291
|
+
ctx_str = f" | ctx: {ref}"
|
|
292
|
+
if h:
|
|
293
|
+
ctx_str += f" (sha256: {h[:12]}…)"
|
|
294
|
+
|
|
295
|
+
return (
|
|
296
|
+
f"[{ts_str}] {tid_tag}"
|
|
297
|
+
f"{self.from_} → {to_str}: {perf} {detail}"
|
|
298
|
+
f"{ctx_str}{det_tag}{pid_tag}{auth_tag}"
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
def __repr__(self) -> str:
|
|
302
|
+
return f"CLowlMessage(p={self.p!r}, from={self.from_!r}, to={self.to!r}, t={self.body.get('t')!r})"
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# ---------------------------------------------------------------------------
|
|
306
|
+
# Factory methods
|
|
307
|
+
# ---------------------------------------------------------------------------
|
|
308
|
+
|
|
309
|
+
def create_req(
|
|
310
|
+
from_: str, to: Union[str, List[str]], cid: str, task: str,
|
|
311
|
+
data: Optional[dict] = None, **kwargs
|
|
312
|
+
) -> CLowlMessage:
|
|
313
|
+
"""Create a REQ (Request) message."""
|
|
314
|
+
return CLowlMessage("REQ", from_, to, cid, task, data or {}, **kwargs)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
def create_ack(
|
|
318
|
+
from_: str, to: str, cid: str, task: str,
|
|
319
|
+
data: Optional[dict] = None, **kwargs
|
|
320
|
+
) -> CLowlMessage:
|
|
321
|
+
"""Create an ACK (Acknowledge) message."""
|
|
322
|
+
return CLowlMessage("ACK", from_, to, cid, task, data or {}, **kwargs)
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
def create_done(
|
|
326
|
+
from_: str, to: str, cid: str, task: str,
|
|
327
|
+
data: Optional[dict] = None, **kwargs
|
|
328
|
+
) -> CLowlMessage:
|
|
329
|
+
"""Create a DONE (Complete) message."""
|
|
330
|
+
return CLowlMessage("DONE", from_, to, cid, task, data or {}, **kwargs)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def create_err(
|
|
334
|
+
from_: str, to: str, cid: str,
|
|
335
|
+
code: str, msg: str, retry: bool = False, **kwargs
|
|
336
|
+
) -> CLowlMessage:
|
|
337
|
+
"""Create an ERR (Error) message."""
|
|
338
|
+
return CLowlMessage("ERR", from_, to, cid, "error", {
|
|
339
|
+
"code": code, "msg": msg, "retry": retry
|
|
340
|
+
}, **kwargs)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def create_dlgt(
|
|
344
|
+
from_: str, to: str, cid: str, task: str,
|
|
345
|
+
delegation_mode: str = "transfer",
|
|
346
|
+
data: Optional[dict] = None, **kwargs
|
|
347
|
+
) -> CLowlMessage:
|
|
348
|
+
"""Create a DLGT (Delegate) message.
|
|
349
|
+
|
|
350
|
+
delegation_mode: 'transfer' | 'fork' | 'assist'
|
|
351
|
+
"""
|
|
352
|
+
if delegation_mode not in VALID_DELEGATION_MODES:
|
|
353
|
+
raise ValueError(f"delegation_mode must be one of {sorted(VALID_DELEGATION_MODES)}")
|
|
354
|
+
d = {"delegation_mode": delegation_mode}
|
|
355
|
+
if data:
|
|
356
|
+
d.update(data)
|
|
357
|
+
return CLowlMessage("DLGT", from_, to, cid, task, d, **kwargs)
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def create_prog(
|
|
361
|
+
from_: str, to: str, cid: str, task: str,
|
|
362
|
+
pct: Optional[int] = None, note: str = "", **kwargs
|
|
363
|
+
) -> CLowlMessage:
|
|
364
|
+
"""Create a PROG (Progress) message."""
|
|
365
|
+
data: dict = {}
|
|
366
|
+
if pct is not None:
|
|
367
|
+
data["pct"] = pct
|
|
368
|
+
if note:
|
|
369
|
+
data["note"] = note
|
|
370
|
+
return CLowlMessage("PROG", from_, to, cid, task, data, **kwargs)
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
def create_caps(
|
|
374
|
+
from_: str, supports: List[str], **kwargs
|
|
375
|
+
) -> CLowlMessage:
|
|
376
|
+
"""Create a CAPS (Capabilities) message. Broadcasts to '*'."""
|
|
377
|
+
return CLowlMessage("CAPS", from_, "*", "system", "capabilities", {
|
|
378
|
+
"supports": supports,
|
|
379
|
+
"clowl": CLOWL_VERSION,
|
|
380
|
+
}, **kwargs)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def create_cncl(
|
|
384
|
+
from_: str, to: str, cid: str, task: str,
|
|
385
|
+
reason: str = "", **kwargs
|
|
386
|
+
) -> CLowlMessage:
|
|
387
|
+
"""Create a CNCL (Cancel) message."""
|
|
388
|
+
data = {"reason": reason} if reason else {}
|
|
389
|
+
return CLowlMessage("CNCL", from_, to, cid, task, data, **kwargs)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
# ---------------------------------------------------------------------------
|
|
393
|
+
# __main__ — example usage
|
|
394
|
+
# ---------------------------------------------------------------------------
|
|
395
|
+
|
|
396
|
+
if __name__ == "__main__":
|
|
397
|
+
print("=== CLowl v0.2 Reference Library — Example Usage ===\n")
|
|
398
|
+
|
|
399
|
+
# Shared conversation / trace IDs
|
|
400
|
+
cid = generate_cid()
|
|
401
|
+
tid = generate_tid()
|
|
402
|
+
print(f"Conversation: {cid}")
|
|
403
|
+
print(f"Trace: {tid}\n")
|
|
404
|
+
|
|
405
|
+
# 1. Radar advertises capabilities
|
|
406
|
+
caps = create_caps("radar", ["search:web", "search:repo", "analyze:trend"])
|
|
407
|
+
print("1. CAPS broadcast:")
|
|
408
|
+
print(caps.to_human())
|
|
409
|
+
print()
|
|
410
|
+
|
|
411
|
+
# 2. Oscar requests a search
|
|
412
|
+
req = create_req(
|
|
413
|
+
"oscar", "radar", cid, "search",
|
|
414
|
+
{"q": "MCP vs A2A comparison", "scope": "web"},
|
|
415
|
+
tid=tid,
|
|
416
|
+
)
|
|
417
|
+
errors = req.validate()
|
|
418
|
+
assert not errors, f"Validation failed: {errors}"
|
|
419
|
+
print("2. REQ (with validation):")
|
|
420
|
+
print(req.to_human())
|
|
421
|
+
print()
|
|
422
|
+
|
|
423
|
+
# 3. Radar acknowledges
|
|
424
|
+
ack = create_ack("radar", "oscar", cid, "search", {"eta": "60s"},
|
|
425
|
+
tid=tid, pid=req.mid)
|
|
426
|
+
print("3. ACK:")
|
|
427
|
+
print(ack.to_human())
|
|
428
|
+
print()
|
|
429
|
+
|
|
430
|
+
# 4. Radar sends progress
|
|
431
|
+
prog = create_prog("radar", "oscar", cid, "search", pct=50,
|
|
432
|
+
note="Indexed 4 of 8 sources",
|
|
433
|
+
tid=tid, pid=req.mid)
|
|
434
|
+
print("4. PROG:")
|
|
435
|
+
print(prog.to_human())
|
|
436
|
+
print()
|
|
437
|
+
|
|
438
|
+
# 5. Radar completes
|
|
439
|
+
done = create_done("radar", "oscar", cid, "search",
|
|
440
|
+
{"result_path": "research/mcp-vs-a2a.md", "sources": 8},
|
|
441
|
+
tid=tid, pid=req.mid, det=True)
|
|
442
|
+
print("5. DONE (deterministic):")
|
|
443
|
+
print(done.to_human())
|
|
444
|
+
print()
|
|
445
|
+
|
|
446
|
+
# 6. Oscar delegates to Muse (transfer mode)
|
|
447
|
+
dlgt = create_dlgt(
|
|
448
|
+
"oscar", "muse", cid, "analyze",
|
|
449
|
+
delegation_mode="transfer",
|
|
450
|
+
data={"target": "content angle", "type": "competitive"},
|
|
451
|
+
tid=tid, pid=done.mid,
|
|
452
|
+
ctx={"ref": "research/mcp-vs-a2a.md", "inline": None, "hash": None},
|
|
453
|
+
)
|
|
454
|
+
print("6. DLGT (transfer):")
|
|
455
|
+
print(dlgt.to_human())
|
|
456
|
+
print()
|
|
457
|
+
|
|
458
|
+
# 7. Error example
|
|
459
|
+
err = create_err("muse", "oscar", cid, "E003",
|
|
460
|
+
"Context file not found: research/mcp-vs-a2a.md",
|
|
461
|
+
retry=True, tid=tid)
|
|
462
|
+
print("7. ERR (retryable):")
|
|
463
|
+
print(err.to_human())
|
|
464
|
+
print()
|
|
465
|
+
|
|
466
|
+
# 8. Broadcast INF to multiple agents
|
|
467
|
+
broadcast = CLowlMessage(
|
|
468
|
+
"INF", "oscar", ["radar", "muse", "ink"], cid, "notify",
|
|
469
|
+
{"message": "Pipeline starting for MCP vs A2A analysis"},
|
|
470
|
+
tid=tid,
|
|
471
|
+
)
|
|
472
|
+
print("8. INF broadcast to [radar, muse, ink]:")
|
|
473
|
+
print(broadcast.to_human())
|
|
474
|
+
print()
|
|
475
|
+
|
|
476
|
+
# 9. Cancel
|
|
477
|
+
cncl = create_cncl("oscar", "radar", cid, "search",
|
|
478
|
+
reason="Scope changed, no longer needed", tid=tid)
|
|
479
|
+
print("9. CNCL:")
|
|
480
|
+
print(cncl.to_human())
|
|
481
|
+
print()
|
|
482
|
+
|
|
483
|
+
# 10. Round-trip: to_dict → from_dict
|
|
484
|
+
d = req.to_dict()
|
|
485
|
+
req2 = CLowlMessage.from_dict(d)
|
|
486
|
+
assert req2.mid == req.mid
|
|
487
|
+
assert req2.body == req.body
|
|
488
|
+
print("10. Round-trip (to_dict → from_dict): ✓")
|
|
489
|
+
print()
|
|
490
|
+
|
|
491
|
+
# 11. Show full JSON for the REQ
|
|
492
|
+
print("11. Full JSON for REQ message:")
|
|
493
|
+
print(req.to_json())
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: clowl
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: CLowl - A structured communication language for AI agent-to-agent messaging
|
|
5
|
+
Author-email: Oscar Sterling Agency <oscar@oscarsterling.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://clowl.dev
|
|
8
|
+
Project-URL: Repository, https://github.com/oscarsterling/clowl
|
|
9
|
+
Project-URL: Documentation, https://clowl.dev
|
|
10
|
+
Keywords: clowl,agent,ai,protocol,communication,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.8
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.8
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# CLowl
|
|
26
|
+
|
|
27
|
+
**A language for AI agents that humans can read.**
|
|
28
|
+
|
|
29
|
+
CLowl (Claw + Talk) is a structured communication protocol for AI agent-to-agent messaging. It defines a minimal JSON schema with typed performatives, message metadata, and context referencing that runs on top of any transport (MCP, A2A, HTTP, WebSocket, stdio, etc.). Every CLowl message has a deterministic English translation, so enterprises can audit any agent conversation and developers can debug multi-agent pipelines in seconds.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Install
|
|
34
|
+
|
|
35
|
+
**TypeScript / Node.js:**
|
|
36
|
+
```bash
|
|
37
|
+
npm install clowl-protocol
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Python:**
|
|
41
|
+
```bash
|
|
42
|
+
pip install clowl
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Quick Start
|
|
48
|
+
|
|
49
|
+
**TypeScript:**
|
|
50
|
+
```typescript
|
|
51
|
+
import { createReq, generateCid, generateTid } from "clowl";
|
|
52
|
+
|
|
53
|
+
const cid = generateCid();
|
|
54
|
+
const req = createReq("oscar", "radar", cid, "search", { q: "MCP vs A2A" }, { tid: generateTid() });
|
|
55
|
+
console.log(req.toHuman());
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Python:**
|
|
59
|
+
```python
|
|
60
|
+
from clowl import create_req, generate_cid, generate_tid
|
|
61
|
+
|
|
62
|
+
cid = generate_cid()
|
|
63
|
+
req = create_req("oscar", "radar", cid, "search", {"q": "MCP vs A2A"}, tid=generate_tid())
|
|
64
|
+
print(req.to_human())
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## The Problem
|
|
70
|
+
|
|
71
|
+
When AI agents talk to each other, nobody knows what they're saying.
|
|
72
|
+
|
|
73
|
+
Agent A sends a blob of unstructured text to Agent B. Agent B replies with another blob. Somewhere in that chain, something goes wrong, and you have no idea what was requested, what was delegated, or where the task died. Multi-agent systems are powerful and opaque in equal measure.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## The Solution
|
|
78
|
+
|
|
79
|
+
CLowl is a language agents speak and humans can read. It defines:
|
|
80
|
+
|
|
81
|
+
- **Structured messages** with typed intent (requests, delegates, errors, progress updates)
|
|
82
|
+
- **Message metadata** for tracing, dedup, and conversation reconstruction
|
|
83
|
+
- **A live translator** that converts CLowl JSON to plain English instantly
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Message Format
|
|
88
|
+
|
|
89
|
+
Every CLowl message is a JSON object with 8 required fields:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"clowl": "0.2",
|
|
94
|
+
"mid": "01914b2c-7abc-...",
|
|
95
|
+
"ts": 1709078400,
|
|
96
|
+
"p": "REQ",
|
|
97
|
+
"from": "oscar",
|
|
98
|
+
"to": "radar",
|
|
99
|
+
"cid": "conv-001",
|
|
100
|
+
"body": {
|
|
101
|
+
"t": "search",
|
|
102
|
+
"d": { "q": "MCP vs A2A", "scope": "web" }
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Optional fields: `tid` (trace ID), `pid` (parent message), `ctx` (context reference), `auth` (auth token), `det` (determinism flag).
|
|
108
|
+
|
|
109
|
+
See the [full spec](spec-v0.2.md) for complete field definitions.
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## Performatives
|
|
114
|
+
|
|
115
|
+
| Code | Name | Meaning |
|
|
116
|
+
|------|------|---------|
|
|
117
|
+
| `REQ` | Request | "Do this thing." Initiates a task. |
|
|
118
|
+
| `INF` | Inform | "Here is information." No action expected. |
|
|
119
|
+
| `ACK` | Acknowledge | "Got it, proceeding." |
|
|
120
|
+
| `ERR` | Error | "Failed. Here's why." Structured error code + message. |
|
|
121
|
+
| `DLGT` | Delegate | "Passing to someone better suited." Requires `delegation_mode`: `transfer`, `fork`, or `assist`. |
|
|
122
|
+
| `DONE` | Complete | "Finished. Here's the result." |
|
|
123
|
+
| `CNCL` | Cancel | "Abort this task." |
|
|
124
|
+
| `QRY` | Query | "What's the status?" or "Give me info without acting." |
|
|
125
|
+
| `PROG` | Progress | "Here's an update on the running task." |
|
|
126
|
+
| `CAPS` | Capabilities | "Here's what I can do." Broadcast on connection. |
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## The Translator
|
|
131
|
+
|
|
132
|
+
Paste CLowl JSON, get English. Paste English, get CLowl JSON. No API needed.
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
$ python translator.py '{"clowl":"0.2","mid":"m001","ts":1709078400,"tid":"t001","p":"REQ","from":"oscar","to":"radar","cid":"c001","body":{"t":"search","d":{"q":"CLowl competitors","scope":"web"}}}'
|
|
136
|
+
|
|
137
|
+
[2026-02-27 12:00:00 UTC] [t001] [m001...] oscar > radar: REQUEST search
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Integration Options
|
|
143
|
+
|
|
144
|
+
**Option 1: Inject the system prompt**
|
|
145
|
+
|
|
146
|
+
Add [`system-prompt-v0.2.md`](system-prompt-v0.2.md) to your agent's system prompt. Any LLM will start generating CLowl messages immediately.
|
|
147
|
+
|
|
148
|
+
**Option 2: Add the JSON schema to your tool calls**
|
|
149
|
+
|
|
150
|
+
Use [`clowl-schema.json`](clowl-schema.json) as a function-calling tool definition. Works with OpenAI, Anthropic, Google, and any local model that supports structured output.
|
|
151
|
+
|
|
152
|
+
**Option 3: Use the libraries**
|
|
153
|
+
|
|
154
|
+
TypeScript and Python libraries provide message creation, validation, state tracking, and translation with zero external dependencies.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Examples
|
|
159
|
+
|
|
160
|
+
See [`examples-v0.2.md`](examples-v0.2.md) for full examples with CLowl JSON and English translations.
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Roadmap
|
|
165
|
+
|
|
166
|
+
**v0.3 (planned)**
|
|
167
|
+
- Three-layer architecture (Semantic / Coordination / Transport)
|
|
168
|
+
- Streaming progress (chunked PROG messages)
|
|
169
|
+
- Cryptographic message signing
|
|
170
|
+
- Go SDK
|
|
171
|
+
|
|
172
|
+
**v1.0 (stable)**
|
|
173
|
+
- Breaking changes locked out
|
|
174
|
+
- Binary encoding option (MessagePack)
|
|
175
|
+
- Production SDK with retry, dedup, and dead-letter queue support
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## Contributing
|
|
180
|
+
|
|
181
|
+
CLowl is an open spec. Contributions welcome:
|
|
182
|
+
|
|
183
|
+
1. Fork the repo
|
|
184
|
+
2. Read [`spec-v0.2.md`](spec-v0.2.md) for the source of truth
|
|
185
|
+
3. Open an issue for design questions before building
|
|
186
|
+
4. PRs should include spec changes + updated examples
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## License
|
|
191
|
+
|
|
192
|
+
MIT
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
Built by [Oscar Sterling Agency](https://clowl.dev) | [clowl.dev](https://clowl.dev)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "clowl"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "CLowl - A structured communication language for AI agent-to-agent messaging"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
requires-python = ">=3.8"
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Oscar Sterling Agency", email = "oscar@oscarsterling.com"}
|
|
14
|
+
]
|
|
15
|
+
keywords = ["clowl", "agent", "ai", "protocol", "communication", "multi-agent"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 3 - Alpha",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.8",
|
|
22
|
+
"Programming Language :: Python :: 3.9",
|
|
23
|
+
"Programming Language :: Python :: 3.10",
|
|
24
|
+
"Programming Language :: Python :: 3.11",
|
|
25
|
+
"Programming Language :: Python :: 3.12",
|
|
26
|
+
"Programming Language :: Python :: 3.13",
|
|
27
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://clowl.dev"
|
|
32
|
+
Repository = "https://github.com/oscarsterling/clowl"
|
|
33
|
+
Documentation = "https://clowl.dev"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools.packages.find]
|
|
36
|
+
where = ["."]
|
|
37
|
+
include = ["clowl*"]
|
clowl-0.2.0/setup.cfg
ADDED