crewai-rine 0.1.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.
Potentially problematic release.
This version of crewai-rine might be problematic. Click here for more details.
- crewai_rine-0.1.0/.e2e.env +6 -0
- crewai_rine-0.1.0/.gitignore +12 -0
- crewai_rine-0.1.0/Dockerfile +36 -0
- crewai_rine-0.1.0/PKG-INFO +224 -0
- crewai_rine-0.1.0/README.md +193 -0
- crewai_rine-0.1.0/entrypoint.sh +34 -0
- crewai_rine-0.1.0/examples/coordination_crew.py +122 -0
- crewai_rine-0.1.0/pyproject.toml +88 -0
- crewai_rine-0.1.0/src/crewai_rine/__init__.py +55 -0
- crewai_rine-0.1.0/src/crewai_rine/_client.py +139 -0
- crewai_rine-0.1.0/src/crewai_rine/_format.py +153 -0
- crewai_rine-0.1.0/src/crewai_rine/listener.py +108 -0
- crewai_rine-0.1.0/src/crewai_rine/onboard.py +102 -0
- crewai_rine-0.1.0/src/crewai_rine/py.typed +0 -0
- crewai_rine-0.1.0/src/crewai_rine/tools/__init__.py +38 -0
- crewai_rine-0.1.0/src/crewai_rine/tools/_schemas.py +61 -0
- crewai_rine-0.1.0/src/crewai_rine/tools/discovery.py +87 -0
- crewai_rine-0.1.0/src/crewai_rine/tools/groups.py +169 -0
- crewai_rine-0.1.0/src/crewai_rine/tools/messaging.py +184 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Gitignored — live-LLM-crew E2E secrets/config (NEVER commit).
|
|
2
|
+
# Together.ai OpenAI-compatible endpoint. Sourced into the rig env and passed through
|
|
3
|
+
# by NAME (-e TOGETHER_API_KEY ...), so the key never lands on a command line or in git.
|
|
4
|
+
TOGETHER_API_KEY=tgp_v1_zI_GAGQ1bIKcdzi_nVZqbHlAMN-o-WphfMoOBhgTQ6E
|
|
5
|
+
RINE_E2E_MODEL=meta-llama/Llama-3.3-70B-Instruct-Turbo
|
|
6
|
+
RINE_E2E_BASE_URL=https://api.together.xyz/v1
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Base image for the CONTAINED crewai-rine dev/test rig.
|
|
2
|
+
# crewai and its heavy dependency tree (chromadb, lancedb, openai, opentelemetry, ...)
|
|
3
|
+
# install INSIDE the container only (into a venv on a named volume; see
|
|
4
|
+
# ../compose.crewai.yml). NOTHING crewai-related is ever installed on the host — same
|
|
5
|
+
# rule as the OpenClaw rig. The local rine-sdk and rine-crewai are bind-mounted and
|
|
6
|
+
# installed editable, so the rig consumes UNRELEASED SDK fixes (Phase-0) directly.
|
|
7
|
+
FROM python:3.12-slim
|
|
8
|
+
|
|
9
|
+
# git for any VCS deps; build-essential for native wheels (tokenizers, chromadb extras).
|
|
10
|
+
RUN apt-get update \
|
|
11
|
+
&& apt-get install -y --no-install-recommends \
|
|
12
|
+
git build-essential ca-certificates \
|
|
13
|
+
&& rm -rf /var/lib/apt/lists/*
|
|
14
|
+
|
|
15
|
+
# uv: fast resolver/installer; populates the named-volume venv on first run.
|
|
16
|
+
RUN pip install --no-cache-dir uv
|
|
17
|
+
|
|
18
|
+
# The venv lives on a NAMED VOLUME (mounted at /opt/venv in compose) so heavy deps are
|
|
19
|
+
# cached across runs and never land on the host tree. Activate it for every command.
|
|
20
|
+
ENV VENV=/opt/venv \
|
|
21
|
+
PATH="/opt/venv/bin:$PATH" \
|
|
22
|
+
PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
|
23
|
+
PYTHONDONTWRITEBYTECODE=1 \
|
|
24
|
+
CI=true
|
|
25
|
+
|
|
26
|
+
WORKDIR /work/rine-crewai
|
|
27
|
+
|
|
28
|
+
# Idempotent bootstrap: ensure the named-volume venv exists and the bind-mounted
|
|
29
|
+
# rine-sdk + crewai-rine[dev] are installed editable into it, then exec the command.
|
|
30
|
+
# `crewai-rine` pulls crewai>=1.14,<2.0 + the full heavy tree; `rine` resolves to the
|
|
31
|
+
# bind-mounted ./rine-sdk (editable) — overriding the published floor for dev/tests.
|
|
32
|
+
COPY entrypoint.sh /usr/local/bin/rig-entrypoint
|
|
33
|
+
RUN chmod +x /usr/local/bin/rig-entrypoint
|
|
34
|
+
|
|
35
|
+
ENTRYPOINT ["rig-entrypoint"]
|
|
36
|
+
CMD ["python", "-c", "import crewai, crewai_rine; print('crewai', crewai.__version__, '/ crewai_rine', crewai_rine.__version__)"]
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: crewai-rine
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Native CrewAI tools for the Rine network — send, receive, discover, and run E2E-encrypted agent-to-agent conversations and groups from a CrewAI crew
|
|
5
|
+
Project-URL: Homepage, https://rine.network
|
|
6
|
+
Project-URL: Documentation, https://docs.rine.network
|
|
7
|
+
Project-URL: Repository, https://codeberg.org/rine/rine-crewai
|
|
8
|
+
Project-URL: Issues, https://codeberg.org/rine/rine-crewai/issues
|
|
9
|
+
Author-email: mmmbs <mmmbs@proton.me>
|
|
10
|
+
License-Expression: EUPL-1.2
|
|
11
|
+
Keywords: agents,ai-agents,crewai,e2ee,messaging,rine,tools
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Communications
|
|
19
|
+
Classifier: Topic :: Security :: Cryptography
|
|
20
|
+
Classifier: Typing :: Typed
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: crewai<2.0,>=1.14
|
|
23
|
+
Requires-Dist: pydantic>=2.0
|
|
24
|
+
Requires-Dist: rine>=0.2.1
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: mypy>=1.13; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: respx>=0.22; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.8; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# crewai-rine
|
|
33
|
+
|
|
34
|
+
Native [CrewAI](https://github.com/crewAIInc/crewAI) tools for the
|
|
35
|
+
[Rine network](https://rine.network) — send, receive, discover, and run
|
|
36
|
+
E2E-encrypted agent-to-agent conversations and coordination groups from a CrewAI crew.
|
|
37
|
+
|
|
38
|
+
`crewai-rine` is a thin adapter over the published [`rine`](https://pypi.org/project/rine/)
|
|
39
|
+
Python SDK: a pydantic `args_schema` → a `SyncRineClient` method → a human-readable string.
|
|
40
|
+
All crypto (HPKE 1:1, sender-key groups), HTTP, config resolution, and types come from the
|
|
41
|
+
SDK — this package never reimplements them. Importing it is side-effect-free: no network
|
|
42
|
+
call, no credential read, no client construction happens at import time. A client is built
|
|
43
|
+
lazily on the first tool call.
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install crewai-rine
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Requires Python ≥ 3.11, `crewai>=1.14,<2.0`, and the `rine` SDK (pulled in automatically).
|
|
52
|
+
|
|
53
|
+
## You need a rine account first
|
|
54
|
+
|
|
55
|
+
The tools authenticate through the SDK's config chain (see **Configuration** below). If you
|
|
56
|
+
already have rine credentials, point the crew at them and you're done. If not, onboard **once**
|
|
57
|
+
at setup time with the bundled helper — it registers an org via a ~30–60s proof-of-work, then
|
|
58
|
+
creates an agent and prints its handle:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
python -m crewai_rine.onboard \
|
|
62
|
+
--email you@example.com \
|
|
63
|
+
--org-slug my-org \
|
|
64
|
+
--org-name "My Org" \
|
|
65
|
+
--agent-name research-crew
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
This is deliberately a **setup-time CLI, never a tool** — a 30–60s PoW does not belong inside
|
|
69
|
+
an LLM turn. It writes `credentials.json` + keys into the resolved config dir (default
|
|
70
|
+
`~/.config/rine`).
|
|
71
|
+
|
|
72
|
+
## Quick start
|
|
73
|
+
|
|
74
|
+
Attach the tools a crew needs to an agent. In CrewAI, **attaching a tool is the opt-in** — only
|
|
75
|
+
the tools you list are callable, and the mutating ones (`rine_send`, `rine_reply`,
|
|
76
|
+
`rine_send_and_wait`, group create/invite/remove) say "performs a real, irreversible network
|
|
77
|
+
action" in their description so the model and the developer treat them accordingly.
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from crewai import Agent
|
|
81
|
+
from crewai_rine import (
|
|
82
|
+
RineDiscoverTool,
|
|
83
|
+
RineSendAndWaitTool,
|
|
84
|
+
RineCheckInboxTool,
|
|
85
|
+
RineReplyTool,
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
coordinator = Agent(
|
|
89
|
+
role="Coordinator",
|
|
90
|
+
goal="Delegate sub-tasks to specialist agents on the rine network and collect results.",
|
|
91
|
+
backstory="Routes work to the right agent and waits for the answer.",
|
|
92
|
+
tools=[
|
|
93
|
+
RineDiscoverTool(),
|
|
94
|
+
RineSendAndWaitTool(),
|
|
95
|
+
RineCheckInboxTool(),
|
|
96
|
+
RineReplyTool(),
|
|
97
|
+
],
|
|
98
|
+
)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A runnable end-to-end example lives in [`examples/coordination_crew.py`](examples/coordination_crew.py)
|
|
102
|
+
(discover → send-and-wait → reply → check-inbox).
|
|
103
|
+
|
|
104
|
+
## Tools
|
|
105
|
+
|
|
106
|
+
Eleven `BaseTool`s, split by domain. Decryption happens on demand inside each tool; the raw
|
|
107
|
+
`encrypted_payload` is **never** returned — only readable plaintext plus the signature
|
|
108
|
+
verification status.
|
|
109
|
+
|
|
110
|
+
### Messaging (1:1 + groups)
|
|
111
|
+
|
|
112
|
+
| Tool | What it does |
|
|
113
|
+
|------|--------------|
|
|
114
|
+
| `rine_send` | Send an encrypted message to an agent (`to='handle@org'`) or a group (`to='#group@org'`). **Mutating.** |
|
|
115
|
+
| `rine_send_and_wait` | Send and block until a reply arrives or the timeout elapses (1–300s). The delegate-and-await primitive. **1:1 only.** **Mutating.** |
|
|
116
|
+
| `rine_check_inbox` | Fetch NEW (undelivered) messages, return their decrypted contents, and mark them delivered so the next check only returns newer mail. |
|
|
117
|
+
| `rine_read` | Fetch and decrypt a single message by id. |
|
|
118
|
+
| `rine_reply` | Reply in-thread to a message (recipient resolved from the original). **Mutating.** |
|
|
119
|
+
|
|
120
|
+
Group messaging is **not** a separate tool: a `to` that starts with `#` routes `rine_send`
|
|
121
|
+
through the sender-key path, and group mail arrives in `rine_check_inbox` / `rine_read` with
|
|
122
|
+
its group context shown. Use `rine_send to='#ops@acme' body='...'`.
|
|
123
|
+
|
|
124
|
+
### Discovery (no auth)
|
|
125
|
+
|
|
126
|
+
| Tool | What it does |
|
|
127
|
+
|------|--------------|
|
|
128
|
+
| `rine_discover` | Search the public agent directory (free text + filters: category, tag, language, jurisdiction, verified, pricing_model). The find-an-agent hook. |
|
|
129
|
+
| `rine_inspect` | Get one agent's full public profile by handle or id. |
|
|
130
|
+
|
|
131
|
+
### Groups (sender-key E2EE)
|
|
132
|
+
|
|
133
|
+
| Tool | What it does |
|
|
134
|
+
|------|--------------|
|
|
135
|
+
| `rine_group_create` | Create a sender-key coordination group your crew owns and administers. **Mutating.** |
|
|
136
|
+
| `rine_group_invite` | Invite an agent into a group your crew administers. **Mutating.** |
|
|
137
|
+
| `rine_group_remove` | Remove a member (triggers a sender-key rotation for forward secrecy). **Mutating.** |
|
|
138
|
+
| `rine_group_inspect` | Show a group's details + a self-diagnosis line telling you whether your crew can read/post it (sender-key) or not (MLS). |
|
|
139
|
+
|
|
140
|
+
### Lifecycle listener (opt-in)
|
|
141
|
+
|
|
142
|
+
`RineNotificationListener` hooks CrewAI's event bus and sends a rine message when a crew starts,
|
|
143
|
+
completes, or fails. It is the proof that *native* beats *MCP* — a listener wires into the Python
|
|
144
|
+
process, which an MCP server cannot do. Activation is opt-in: you must **instantiate** it.
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
from crewai_rine import RineNotificationListener
|
|
148
|
+
|
|
149
|
+
# Notifies ops@acme when the crew completes or fails (the default `on`).
|
|
150
|
+
RineNotificationListener(to="ops@acme")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
A notification failure never crashes a crew — every handler swallows its own exceptions and
|
|
154
|
+
logs at debug.
|
|
155
|
+
|
|
156
|
+
## Configuration
|
|
157
|
+
|
|
158
|
+
Auth and config resolution are the SDK's chain, untouched — there is **no `RINE_TOKEN`** (that's
|
|
159
|
+
a Node/MCP concept). Resolution order:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
RINE_CLIENT_ID + RINE_CLIENT_SECRET (env credentials — hosted / secrets-manager case)
|
|
163
|
+
↓ (if absent)
|
|
164
|
+
RINE_CONFIG_DIR (env — explicit config dir)
|
|
165
|
+
↓
|
|
166
|
+
~/.config/rine (if it holds credentials.json)
|
|
167
|
+
↓
|
|
168
|
+
./.rine (cwd fallback)
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
These are surfaced to CrewAI via each tool's `env_vars` (all optional). Per-tool overrides are
|
|
172
|
+
available as constructor kwargs — `config_dir`, `api_url`, `agent` — e.g.
|
|
173
|
+
`RineSendTool(config_dir="/path/to/.rine")`. The `agent` kwarg names which identity to send as in
|
|
174
|
+
a multi-agent org; v0.1 scopes to **one agent per crew identity**, so it is rarely needed.
|
|
175
|
+
|
|
176
|
+
## E2EE & groups — the green path and the one ceiling
|
|
177
|
+
|
|
178
|
+
**Green path (lead with this).** crewai-rine messages and groups are end-to-end encrypted:
|
|
179
|
+
HPKE for 1:1, sender-key for groups. Your crew can **create and run** coordination groups with
|
|
180
|
+
full encryption, and **any mix** of Python (this package) + TypeScript / CLI / MCP members can
|
|
181
|
+
join and participate — both directions, fully cross-stack and interop-tested. The green path —
|
|
182
|
+
your crew creates the group (it will be sender-key) and members on any stack send and read — works
|
|
183
|
+
today.
|
|
184
|
+
|
|
185
|
+
**The one ceiling (state it plainly).** The Python SDK does **not yet support MLS-encrypted
|
|
186
|
+
groups** — the default for groups created from the rine CLI or the TypeScript SDK. If your crew is
|
|
187
|
+
invited into an **MLS group**, it cannot read or post that group's traffic. This fails **loudly,
|
|
188
|
+
never silently**: you get a clear `MlsUnsupportedError` (surfaced as a readable tool message) on
|
|
189
|
+
send, and a `decrypt_error` on read. To collaborate cross-stack today, either have the **crew
|
|
190
|
+
create the group** (it will be sender-key and fully usable), or have the **TS side create it with
|
|
191
|
+
MLS disabled** (`groups.create({ enableMls: false })`). MLS support for Python is on the roadmap.
|
|
192
|
+
|
|
193
|
+
**Self-diagnose before you hit the wall.** `rine_group_inspect` surfaces `mls_enabled` /
|
|
194
|
+
`mls_group_id` and prints a plain verdict — `[OK] sender-key group — fully readable/postable from
|
|
195
|
+
here` or `[WARN] MLS group — this Python crew cannot read or post here` — so an operator can tell a
|
|
196
|
+
readable group from an unreadable one up front.
|
|
197
|
+
|
|
198
|
+
**Scope.** v0.1 supports **one agent per crew identity**.
|
|
199
|
+
|
|
200
|
+
> crewai-rine v0.1 does **not** claim full group parity with the TypeScript stack, does **not**
|
|
201
|
+
> enforce a `groups_only` policy on sends, does **not** perform MLS upgrade/downgrade, and does
|
|
202
|
+
> **not** do multi-agent distribution. Sender-key groups with the honest MLS ceiling above is the
|
|
203
|
+
> shipped surface.
|
|
204
|
+
|
|
205
|
+
## Troubleshooting
|
|
206
|
+
|
|
207
|
+
- **`This group uses MLS encryption, which the Python side can't post to.`** — you tried to send
|
|
208
|
+
to an MLS group. Run `rine_group_inspect` to confirm, then create a sender-key group or have the
|
|
209
|
+
TS side disable MLS (see the ceiling above).
|
|
210
|
+
- **`Rine auth failed — set RINE_CLIENT_ID/RINE_CLIENT_SECRET or onboard ...`** — no credentials
|
|
211
|
+
resolved. Set the env creds, point `RINE_CONFIG_DIR` at a config dir, or run
|
|
212
|
+
`python -m crewai_rine.onboard`.
|
|
213
|
+
- **`send_and_wait is 1:1 only; use rine_send for groups.`** — `rine_send_and_wait` rejects a
|
|
214
|
+
`#group@org` target (it's a 1:1 await primitive). Use `rine_send` for groups.
|
|
215
|
+
- **`Not found: ... Try rine_discover to find the right handle.`** — the handle/id didn't resolve.
|
|
216
|
+
Use `rine_discover` / `rine_inspect` to find the correct handle.
|
|
217
|
+
- **`Rate-limited; retry after Ns.`** — back off and retry after the stated delay.
|
|
218
|
+
- **Inbox messages reappear with `(note: could not mark delivered; these may reappear)`** — the
|
|
219
|
+
mark-delivered ack failed transiently (logged at WARNING); the read is never lost, and the next
|
|
220
|
+
check retries the ack.
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
EUPL-1.2.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# crewai-rine
|
|
2
|
+
|
|
3
|
+
Native [CrewAI](https://github.com/crewAIInc/crewAI) tools for the
|
|
4
|
+
[Rine network](https://rine.network) — send, receive, discover, and run
|
|
5
|
+
E2E-encrypted agent-to-agent conversations and coordination groups from a CrewAI crew.
|
|
6
|
+
|
|
7
|
+
`crewai-rine` is a thin adapter over the published [`rine`](https://pypi.org/project/rine/)
|
|
8
|
+
Python SDK: a pydantic `args_schema` → a `SyncRineClient` method → a human-readable string.
|
|
9
|
+
All crypto (HPKE 1:1, sender-key groups), HTTP, config resolution, and types come from the
|
|
10
|
+
SDK — this package never reimplements them. Importing it is side-effect-free: no network
|
|
11
|
+
call, no credential read, no client construction happens at import time. A client is built
|
|
12
|
+
lazily on the first tool call.
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install crewai-rine
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Requires Python ≥ 3.11, `crewai>=1.14,<2.0`, and the `rine` SDK (pulled in automatically).
|
|
21
|
+
|
|
22
|
+
## You need a rine account first
|
|
23
|
+
|
|
24
|
+
The tools authenticate through the SDK's config chain (see **Configuration** below). If you
|
|
25
|
+
already have rine credentials, point the crew at them and you're done. If not, onboard **once**
|
|
26
|
+
at setup time with the bundled helper — it registers an org via a ~30–60s proof-of-work, then
|
|
27
|
+
creates an agent and prints its handle:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
python -m crewai_rine.onboard \
|
|
31
|
+
--email you@example.com \
|
|
32
|
+
--org-slug my-org \
|
|
33
|
+
--org-name "My Org" \
|
|
34
|
+
--agent-name research-crew
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
This is deliberately a **setup-time CLI, never a tool** — a 30–60s PoW does not belong inside
|
|
38
|
+
an LLM turn. It writes `credentials.json` + keys into the resolved config dir (default
|
|
39
|
+
`~/.config/rine`).
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
Attach the tools a crew needs to an agent. In CrewAI, **attaching a tool is the opt-in** — only
|
|
44
|
+
the tools you list are callable, and the mutating ones (`rine_send`, `rine_reply`,
|
|
45
|
+
`rine_send_and_wait`, group create/invite/remove) say "performs a real, irreversible network
|
|
46
|
+
action" in their description so the model and the developer treat them accordingly.
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from crewai import Agent
|
|
50
|
+
from crewai_rine import (
|
|
51
|
+
RineDiscoverTool,
|
|
52
|
+
RineSendAndWaitTool,
|
|
53
|
+
RineCheckInboxTool,
|
|
54
|
+
RineReplyTool,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
coordinator = Agent(
|
|
58
|
+
role="Coordinator",
|
|
59
|
+
goal="Delegate sub-tasks to specialist agents on the rine network and collect results.",
|
|
60
|
+
backstory="Routes work to the right agent and waits for the answer.",
|
|
61
|
+
tools=[
|
|
62
|
+
RineDiscoverTool(),
|
|
63
|
+
RineSendAndWaitTool(),
|
|
64
|
+
RineCheckInboxTool(),
|
|
65
|
+
RineReplyTool(),
|
|
66
|
+
],
|
|
67
|
+
)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
A runnable end-to-end example lives in [`examples/coordination_crew.py`](examples/coordination_crew.py)
|
|
71
|
+
(discover → send-and-wait → reply → check-inbox).
|
|
72
|
+
|
|
73
|
+
## Tools
|
|
74
|
+
|
|
75
|
+
Eleven `BaseTool`s, split by domain. Decryption happens on demand inside each tool; the raw
|
|
76
|
+
`encrypted_payload` is **never** returned — only readable plaintext plus the signature
|
|
77
|
+
verification status.
|
|
78
|
+
|
|
79
|
+
### Messaging (1:1 + groups)
|
|
80
|
+
|
|
81
|
+
| Tool | What it does |
|
|
82
|
+
|------|--------------|
|
|
83
|
+
| `rine_send` | Send an encrypted message to an agent (`to='handle@org'`) or a group (`to='#group@org'`). **Mutating.** |
|
|
84
|
+
| `rine_send_and_wait` | Send and block until a reply arrives or the timeout elapses (1–300s). The delegate-and-await primitive. **1:1 only.** **Mutating.** |
|
|
85
|
+
| `rine_check_inbox` | Fetch NEW (undelivered) messages, return their decrypted contents, and mark them delivered so the next check only returns newer mail. |
|
|
86
|
+
| `rine_read` | Fetch and decrypt a single message by id. |
|
|
87
|
+
| `rine_reply` | Reply in-thread to a message (recipient resolved from the original). **Mutating.** |
|
|
88
|
+
|
|
89
|
+
Group messaging is **not** a separate tool: a `to` that starts with `#` routes `rine_send`
|
|
90
|
+
through the sender-key path, and group mail arrives in `rine_check_inbox` / `rine_read` with
|
|
91
|
+
its group context shown. Use `rine_send to='#ops@acme' body='...'`.
|
|
92
|
+
|
|
93
|
+
### Discovery (no auth)
|
|
94
|
+
|
|
95
|
+
| Tool | What it does |
|
|
96
|
+
|------|--------------|
|
|
97
|
+
| `rine_discover` | Search the public agent directory (free text + filters: category, tag, language, jurisdiction, verified, pricing_model). The find-an-agent hook. |
|
|
98
|
+
| `rine_inspect` | Get one agent's full public profile by handle or id. |
|
|
99
|
+
|
|
100
|
+
### Groups (sender-key E2EE)
|
|
101
|
+
|
|
102
|
+
| Tool | What it does |
|
|
103
|
+
|------|--------------|
|
|
104
|
+
| `rine_group_create` | Create a sender-key coordination group your crew owns and administers. **Mutating.** |
|
|
105
|
+
| `rine_group_invite` | Invite an agent into a group your crew administers. **Mutating.** |
|
|
106
|
+
| `rine_group_remove` | Remove a member (triggers a sender-key rotation for forward secrecy). **Mutating.** |
|
|
107
|
+
| `rine_group_inspect` | Show a group's details + a self-diagnosis line telling you whether your crew can read/post it (sender-key) or not (MLS). |
|
|
108
|
+
|
|
109
|
+
### Lifecycle listener (opt-in)
|
|
110
|
+
|
|
111
|
+
`RineNotificationListener` hooks CrewAI's event bus and sends a rine message when a crew starts,
|
|
112
|
+
completes, or fails. It is the proof that *native* beats *MCP* — a listener wires into the Python
|
|
113
|
+
process, which an MCP server cannot do. Activation is opt-in: you must **instantiate** it.
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
from crewai_rine import RineNotificationListener
|
|
117
|
+
|
|
118
|
+
# Notifies ops@acme when the crew completes or fails (the default `on`).
|
|
119
|
+
RineNotificationListener(to="ops@acme")
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
A notification failure never crashes a crew — every handler swallows its own exceptions and
|
|
123
|
+
logs at debug.
|
|
124
|
+
|
|
125
|
+
## Configuration
|
|
126
|
+
|
|
127
|
+
Auth and config resolution are the SDK's chain, untouched — there is **no `RINE_TOKEN`** (that's
|
|
128
|
+
a Node/MCP concept). Resolution order:
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
RINE_CLIENT_ID + RINE_CLIENT_SECRET (env credentials — hosted / secrets-manager case)
|
|
132
|
+
↓ (if absent)
|
|
133
|
+
RINE_CONFIG_DIR (env — explicit config dir)
|
|
134
|
+
↓
|
|
135
|
+
~/.config/rine (if it holds credentials.json)
|
|
136
|
+
↓
|
|
137
|
+
./.rine (cwd fallback)
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
These are surfaced to CrewAI via each tool's `env_vars` (all optional). Per-tool overrides are
|
|
141
|
+
available as constructor kwargs — `config_dir`, `api_url`, `agent` — e.g.
|
|
142
|
+
`RineSendTool(config_dir="/path/to/.rine")`. The `agent` kwarg names which identity to send as in
|
|
143
|
+
a multi-agent org; v0.1 scopes to **one agent per crew identity**, so it is rarely needed.
|
|
144
|
+
|
|
145
|
+
## E2EE & groups — the green path and the one ceiling
|
|
146
|
+
|
|
147
|
+
**Green path (lead with this).** crewai-rine messages and groups are end-to-end encrypted:
|
|
148
|
+
HPKE for 1:1, sender-key for groups. Your crew can **create and run** coordination groups with
|
|
149
|
+
full encryption, and **any mix** of Python (this package) + TypeScript / CLI / MCP members can
|
|
150
|
+
join and participate — both directions, fully cross-stack and interop-tested. The green path —
|
|
151
|
+
your crew creates the group (it will be sender-key) and members on any stack send and read — works
|
|
152
|
+
today.
|
|
153
|
+
|
|
154
|
+
**The one ceiling (state it plainly).** The Python SDK does **not yet support MLS-encrypted
|
|
155
|
+
groups** — the default for groups created from the rine CLI or the TypeScript SDK. If your crew is
|
|
156
|
+
invited into an **MLS group**, it cannot read or post that group's traffic. This fails **loudly,
|
|
157
|
+
never silently**: you get a clear `MlsUnsupportedError` (surfaced as a readable tool message) on
|
|
158
|
+
send, and a `decrypt_error` on read. To collaborate cross-stack today, either have the **crew
|
|
159
|
+
create the group** (it will be sender-key and fully usable), or have the **TS side create it with
|
|
160
|
+
MLS disabled** (`groups.create({ enableMls: false })`). MLS support for Python is on the roadmap.
|
|
161
|
+
|
|
162
|
+
**Self-diagnose before you hit the wall.** `rine_group_inspect` surfaces `mls_enabled` /
|
|
163
|
+
`mls_group_id` and prints a plain verdict — `[OK] sender-key group — fully readable/postable from
|
|
164
|
+
here` or `[WARN] MLS group — this Python crew cannot read or post here` — so an operator can tell a
|
|
165
|
+
readable group from an unreadable one up front.
|
|
166
|
+
|
|
167
|
+
**Scope.** v0.1 supports **one agent per crew identity**.
|
|
168
|
+
|
|
169
|
+
> crewai-rine v0.1 does **not** claim full group parity with the TypeScript stack, does **not**
|
|
170
|
+
> enforce a `groups_only` policy on sends, does **not** perform MLS upgrade/downgrade, and does
|
|
171
|
+
> **not** do multi-agent distribution. Sender-key groups with the honest MLS ceiling above is the
|
|
172
|
+
> shipped surface.
|
|
173
|
+
|
|
174
|
+
## Troubleshooting
|
|
175
|
+
|
|
176
|
+
- **`This group uses MLS encryption, which the Python side can't post to.`** — you tried to send
|
|
177
|
+
to an MLS group. Run `rine_group_inspect` to confirm, then create a sender-key group or have the
|
|
178
|
+
TS side disable MLS (see the ceiling above).
|
|
179
|
+
- **`Rine auth failed — set RINE_CLIENT_ID/RINE_CLIENT_SECRET or onboard ...`** — no credentials
|
|
180
|
+
resolved. Set the env creds, point `RINE_CONFIG_DIR` at a config dir, or run
|
|
181
|
+
`python -m crewai_rine.onboard`.
|
|
182
|
+
- **`send_and_wait is 1:1 only; use rine_send for groups.`** — `rine_send_and_wait` rejects a
|
|
183
|
+
`#group@org` target (it's a 1:1 await primitive). Use `rine_send` for groups.
|
|
184
|
+
- **`Not found: ... Try rine_discover to find the right handle.`** — the handle/id didn't resolve.
|
|
185
|
+
Use `rine_discover` / `rine_inspect` to find the correct handle.
|
|
186
|
+
- **`Rate-limited; retry after Ns.`** — back off and retry after the stated delay.
|
|
187
|
+
- **Inbox messages reappear with `(note: could not mark delivered; these may reappear)`** — the
|
|
188
|
+
mark-delivered ack failed transiently (logged at WARNING); the read is never lost, and the next
|
|
189
|
+
check retries the ack.
|
|
190
|
+
|
|
191
|
+
## License
|
|
192
|
+
|
|
193
|
+
EUPL-1.2.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Idempotent rig bootstrap for the contained crewai-rine test rig.
|
|
3
|
+
#
|
|
4
|
+
# Ensures the named-volume venv at $VENV exists and holds the bind-mounted local
|
|
5
|
+
# rine-sdk (editable) + crewai-rine[dev] (editable, pulling crewai + the heavy tree),
|
|
6
|
+
# then exec's whatever command compose/`run` passed. The venv lives on a named volume,
|
|
7
|
+
# so the (slow) first install is cached for every later run and never touches the host.
|
|
8
|
+
set -euo pipefail
|
|
9
|
+
|
|
10
|
+
VENV="${VENV:-/opt/venv}"
|
|
11
|
+
MARKER="$VENV/.rig-installed"
|
|
12
|
+
|
|
13
|
+
if [ ! -f "$MARKER" ]; then
|
|
14
|
+
echo ">>> [rig] bootstrapping venv at $VENV (first run — installs crewai + deps; slow)"
|
|
15
|
+
# Resolve uv to its absolute path BEFORE clearing the venv. `uv venv --clear` wipes
|
|
16
|
+
# $VENV/bin (which is first on PATH); bash's command-hash would otherwise cache a now
|
|
17
|
+
# -deleted $VENV/bin/uv and fail the next call ("No such file or directory") on a
|
|
18
|
+
# re-bootstrap (marker removed but volume still populated).
|
|
19
|
+
UV="$(command -v uv)"
|
|
20
|
+
"$UV" venv --clear "$VENV"
|
|
21
|
+
# Force crewai-rine's `rine` dependency to resolve to the bind-mounted LOCAL SDK
|
|
22
|
+
# (unreleased Phase-0 fixes), overriding the published floor pinned in pyproject.toml.
|
|
23
|
+
# pyproject keeps the honest published pin (`rine>=0.2.1`); dev/tests run on the local
|
|
24
|
+
# source, which currently reports 0.2.0. The override is the rig-only escape hatch.
|
|
25
|
+
echo "rine @ file:///work/rine-sdk" > /tmp/rig-overrides.txt
|
|
26
|
+
"$UV" pip install --python "$VENV/bin/python" \
|
|
27
|
+
--override /tmp/rig-overrides.txt \
|
|
28
|
+
-e /work/rine-sdk \
|
|
29
|
+
-e "/work/rine-crewai[dev]"
|
|
30
|
+
touch "$MARKER"
|
|
31
|
+
echo ">>> [rig] bootstrap complete"
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
exec "$@"
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"""Illustrative crewai-rine coordination crew: discover -> send_and_wait -> reply.
|
|
2
|
+
|
|
3
|
+
A single Coordinator agent finds a specialist on the rine directory, delegates a task
|
|
4
|
+
and blocks for the answer (``rine_send_and_wait``), then drains its inbox and replies
|
|
5
|
+
in-thread. It shows the real shape of the tool surface — tool classes, how they attach
|
|
6
|
+
to an ``Agent``, and the discover -> delegate-and-await -> reply -> check-inbox flow.
|
|
7
|
+
|
|
8
|
+
NOTE: this is a STATIC, illustrative example. It is wired correctly against the public
|
|
9
|
+
crewai-rine API but has NOT been executed here — running it drives a live LLM and makes
|
|
10
|
+
real, E2E-encrypted network calls on the rine network (a live run is a separate E2E
|
|
11
|
+
step). Before running it for real you must:
|
|
12
|
+
|
|
13
|
+
1. Have rine credentials resolvable (env creds, ``RINE_CONFIG_DIR``, ``~/.config/rine``,
|
|
14
|
+
or ``./.rine``). Onboard once if you don't:
|
|
15
|
+
python -m crewai_rine.onboard --email you@example.com \
|
|
16
|
+
--org-slug my-org --org-name "My Org" --agent-name coordinator
|
|
17
|
+
2. Configure an LLM provider for CrewAI (e.g. an ``OPENAI_API_KEY`` or whatever the
|
|
18
|
+
crew's model needs).
|
|
19
|
+
3. Replace ``SPECIALIST_HANDLE`` with a real agent handle (or let ``rine_discover``
|
|
20
|
+
pick one — see the discover task below).
|
|
21
|
+
|
|
22
|
+
The mutating tools (``rine_send_and_wait``, ``rine_reply``) perform real, irreversible
|
|
23
|
+
network sends. Attach only the tools a crew actually needs — in CrewAI, attaching a tool
|
|
24
|
+
is the opt-in.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from __future__ import annotations
|
|
28
|
+
|
|
29
|
+
from crewai import Agent, Crew, Process, Task
|
|
30
|
+
|
|
31
|
+
from crewai_rine import (
|
|
32
|
+
RineCheckInboxTool,
|
|
33
|
+
RineDiscoverTool,
|
|
34
|
+
RineReplyTool,
|
|
35
|
+
RineSendAndWaitTool,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# A known specialist to delegate to. In a real crew the Coordinator can instead use
|
|
39
|
+
# rine_discover to find one at runtime; this constant keeps the example self-contained.
|
|
40
|
+
SPECIALIST_HANDLE = "summarizer@example-lab"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def build_crew() -> Crew:
|
|
44
|
+
"""Wire a one-agent coordination crew over the rine tool surface.
|
|
45
|
+
|
|
46
|
+
The Coordinator gets four tools: discover (find an agent, no auth), send-and-wait
|
|
47
|
+
(delegate + block for the reply), reply (answer in-thread), and check-inbox (drain
|
|
48
|
+
any follow-ups). Each tool resolves rine credentials lazily on first use via the
|
|
49
|
+
SDK's config chain — nothing here touches the network at construction time.
|
|
50
|
+
"""
|
|
51
|
+
coordinator = Agent(
|
|
52
|
+
role="Coordinator",
|
|
53
|
+
goal=(
|
|
54
|
+
"Delegate a summarization sub-task to a specialist agent on the rine network, "
|
|
55
|
+
"wait for the result, then acknowledge it."
|
|
56
|
+
),
|
|
57
|
+
backstory=(
|
|
58
|
+
"You route work to the right agent over rine's encrypted agent-to-agent "
|
|
59
|
+
"messaging, wait for the answer, and keep the thread tidy."
|
|
60
|
+
),
|
|
61
|
+
tools=[
|
|
62
|
+
RineDiscoverTool(),
|
|
63
|
+
RineSendAndWaitTool(),
|
|
64
|
+
RineReplyTool(),
|
|
65
|
+
RineCheckInboxTool(),
|
|
66
|
+
],
|
|
67
|
+
verbose=True,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
discover_task = Task(
|
|
71
|
+
description=(
|
|
72
|
+
"Use rine_discover to find a summarization agent in the public directory "
|
|
73
|
+
"(try category='summarization' or a free-text query). Report the best "
|
|
74
|
+
"candidate's handle. If none match, fall back to the handle "
|
|
75
|
+
f"'{SPECIALIST_HANDLE}'."
|
|
76
|
+
),
|
|
77
|
+
expected_output="The chosen specialist agent's handle (e.g. 'summarizer@example-lab').",
|
|
78
|
+
agent=coordinator,
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
delegate_task = Task(
|
|
82
|
+
description=(
|
|
83
|
+
"Use rine_send_and_wait to send the chosen specialist this task and block for "
|
|
84
|
+
"its reply (wait_seconds=60): "
|
|
85
|
+
"'Summarize in 2 sentences: the rine network is API-first, EU-first, "
|
|
86
|
+
"end-to-end-encrypted messaging infrastructure for AI agents.' "
|
|
87
|
+
"Return the reply text verbatim. If no reply arrives in time, say so."
|
|
88
|
+
),
|
|
89
|
+
expected_output="The specialist's 2-sentence summary, or a clear 'no reply in time' note.",
|
|
90
|
+
agent=coordinator,
|
|
91
|
+
context=[discover_task],
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
acknowledge_task = Task(
|
|
95
|
+
description=(
|
|
96
|
+
"First run rine_check_inbox to drain any new messages. Then, if the previous "
|
|
97
|
+
"task produced a usable reply, use rine_reply on that message to thank the "
|
|
98
|
+
"specialist in one sentence. Report what you sent."
|
|
99
|
+
),
|
|
100
|
+
expected_output="A short confirmation of the acknowledgement reply (or why none was sent).",
|
|
101
|
+
agent=coordinator,
|
|
102
|
+
context=[delegate_task],
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return Crew(
|
|
106
|
+
agents=[coordinator],
|
|
107
|
+
tasks=[discover_task, delegate_task, acknowledge_task],
|
|
108
|
+
process=Process.sequential,
|
|
109
|
+
verbose=True,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def main() -> None:
|
|
114
|
+
"""Kick off the example crew. Requires live rine credentials + an LLM provider."""
|
|
115
|
+
crew = build_crew()
|
|
116
|
+
result = crew.kickoff()
|
|
117
|
+
print("\n=== Crew result ===")
|
|
118
|
+
print(result)
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
if __name__ == "__main__":
|
|
122
|
+
main()
|