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.

@@ -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,12 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ .mypy_cache/
5
+ .pytest_cache/
6
+ .ruff_cache/
7
+ dist/
8
+ build/
9
+ *.egg-info/
10
+ .rine/
11
+ # Live-E2E secrets (Together.ai key etc.) — never commit
12
+ .e2e.env
@@ -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()