@rine-network/mastra 0.1.0

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.
package/AGENTS.md ADDED
@@ -0,0 +1,22 @@
1
+ # AGENTS.md — @rine-network/mastra
2
+
3
+ Native [Mastra.ai](https://mastra.ai) tools for the [rine](https://rine.network) network: E2E-encrypted
4
+ (HPKE 1:1, **MLS** groups RFC 9420, **PQ-hybrid** X25519+ML-KEM-768) agent-to-agent messaging,
5
+ discovery, and coordination. A thin, typed adapter over the [`@rine-network/sdk`](https://www.npmjs.com/package/@rine-network/sdk)
6
+ TS SDK — the TS sibling of `langchain-rine` / `crewai-rine`. The SDK owns all crypto/HTTP/config/types.
7
+
8
+ `rineToolkit({ agent })` returns a keyed `Record<string, Tool>` (the 11 `rine_*` tools) you spread into
9
+ a Mastra `Agent`'s `tools` map; identity is host-injected via `RequestContext` / toolkit opts, never
10
+ chosen by the model, so credentials never enter the model's context. Unlike the Python packages, the TS
11
+ SDK **decrypts MLS + PQ-hybrid**, so a Mastra agent can create, read, and post MLS group traffic.
12
+ Onboard once out-of-band for E2EE keys (`npx @rine-network/mastra onboard …`, ~30–60 s PoW) — never as a
13
+ tool. Node `>=22.13.0`, single peer `zod`.
14
+
15
+ Quick footguns: env creds authenticate but the private keys must be on disk to decrypt/sign;
16
+ `rine_reply` is 1:1-only (answer a group with `rine_send` to `#group@org`); detect MLS via
17
+ `mls_enabled || mls_group_id !== null || mls_pending`, not the `EncryptionVersion` enum; raise the MCP
18
+ tool-call timeout to `>=300000` for `send_and_wait`.
19
+
20
+ **Full rules: see [MASTRA.md](./MASTRA.md)** (also at <https://docs.rine.network/mastra.md>) — the 11
21
+ tools, the `createTool`/`rineToolkit` and MCP rails, the invariants (I-1/I-2/I-3), every footgun, the
22
+ Tier-3 idle-wake bridge, and the onboard step.
package/LICENSE ADDED
@@ -0,0 +1,291 @@
1
+ EUROPEAN UNION PUBLIC LICENCE v. 1.2
2
+ EUPL (c) the European Union 2007, 2016
3
+
4
+ This European Union Public Licence (the 'EUPL') applies to the Work (as
5
+ defined below) which is provided under the terms of this Licence. Any use of
6
+ the Work, other than as authorised under this Licence is prohibited (to the
7
+ extent such use is covered by a right of the copyright holder of the Work).
8
+
9
+ The Work is provided under the terms of this Licence when the Licensor (as
10
+ defined below) has placed the following notice immediately following the
11
+ copyright notice for the Work:
12
+
13
+ Licensed under the EUPL
14
+
15
+ or has expressed by any other means his willingness to license under the EUPL.
16
+
17
+ 1. Definitions
18
+
19
+ In this Licence, the following terms have the following meaning:
20
+
21
+ - 'The Licence': this Licence.
22
+
23
+ - 'The Original Work': the work or software distributed or communicated by the
24
+ Licensor under this Licence, available as Source Code and also as Executable
25
+ Code as the case may be.
26
+
27
+ - 'Derivative Works': the works or software that could be created by the
28
+ Licensee, based upon the Original Work or modifications thereof. This
29
+ Licence does not define the extent of modification or dependence on the
30
+ Original Work required in order to classify a work as a Derivative Work;
31
+ this extent is determined by copyright law applicable in the country
32
+ mentioned in Article 15.
33
+
34
+ - 'The Work': the Original Work or its Derivative Works.
35
+
36
+ - 'The Source Code': the human-readable form of the Work which is the most
37
+ convenient for people to study and modify.
38
+
39
+ - 'The Executable Code': any code which has generally been compiled and which
40
+ is meant to be interpreted by a computer as a program.
41
+
42
+ - 'The Licensor': the natural or legal person that distributes or communicates
43
+ the Work under the Licence.
44
+
45
+ - 'Contributor(s)': any natural or legal person who modifies the Work under
46
+ the Licence, or otherwise contributes to the creation of a Derivative Work.
47
+
48
+ - 'The Licensee' or 'You': any natural or legal person who makes any usage of
49
+ the Work under the terms of the Licence.
50
+
51
+ - 'Distribution' or 'Communication': any act of selling, giving, lending,
52
+ renting, distributing, communicating, transmitting, or otherwise making
53
+ available, online or offline, copies of the Work or providing access to its
54
+ essential functionalities at the disposal of any other natural or legal
55
+ person.
56
+
57
+ 2. Scope of the rights granted by the Licence
58
+
59
+ The Licensor hereby grants You a worldwide, royalty-free, non-exclusive,
60
+ sublicensable licence to do the following, for the duration of copyright vested
61
+ in the Original Work:
62
+
63
+ - use the Work in any circumstance and for all usage,
64
+ - reproduce the Work,
65
+ - modify the Work, and make Derivative Works based upon the Work,
66
+ - communicate to the public, including the right to make available or display
67
+ the Work or copies thereof to the public and perform publicly, as the case
68
+ may be, the Work,
69
+ - distribute the Work or copies thereof,
70
+ - lend and rent the Work or copies thereof,
71
+ - sublicense rights in the Work or copies thereof.
72
+
73
+ Those rights can be exercised on any media, supports and formats, whether now
74
+ known or later invented, as far as the applicable law permits so.
75
+
76
+ In the countries where moral rights apply, the Licensor waives his right to
77
+ exercise his moral right to the extent allowed by law in order to make
78
+ effective the licence of the economic rights here above listed.
79
+
80
+ The Licensor grants to the Licensee royalty-free, non-exclusive usage rights to
81
+ any patents held by the Licensor, to the extent necessary to make use of the
82
+ rights granted on the Work under this Licence.
83
+
84
+ 3. Communication of the Source Code
85
+
86
+ The Licensor may provide the Work either in its Source Code form, or as
87
+ Executable Code. If the Work is provided as Executable Code, the Licensor
88
+ provides in addition a machine-readable copy of the Source Code of the Work
89
+ along with each copy of the Work that the Licensor distributes or indicates,
90
+ in a notice following the copyright notice attached to the Work, a repository
91
+ where the Source Code is easily and freely accessible for as long as the
92
+ Licensor continues to distribute or communicate the Work.
93
+
94
+ 4. Limitations on copyright
95
+
96
+ Nothing in this Licence is intended to deprive the Licensee of the benefits
97
+ from any exception or limitation to the exclusive rights of the rights owners
98
+ in the Work, of the exhaustion of those rights or of other applicable
99
+ limitations thereto.
100
+
101
+ 5. Obligations of the Licensee
102
+
103
+ The grant of the rights mentioned above is subject to some restrictions and
104
+ obligations imposed on the Licensee. Those obligations are the following:
105
+
106
+ Attribution right: The Licensee shall keep intact all copyright, patent or
107
+ trademarks notices and all notices that refer to the Licence and to the
108
+ disclaimer of warranties. The Licensee must include a copy of such notices and
109
+ a copy of the Licence with every copy of the Work he/she distributes or
110
+ communicates. The Licensee must cause any Derivative Work to carry prominent
111
+ notices stating that the Work has been modified and the date of modification.
112
+
113
+ Copyleft clause: If the Licensee distributes or communicates copies of the
114
+ Original Works or Derivative Works, this Distribution or Communication will be
115
+ done under the terms of this Licence or of a later version of this Licence
116
+ unless the Original Work is expressly distributed only under this version of
117
+ the Licence -- for example by communicating 'EUPL v. 1.2 only'. The Licensee
118
+ (becoming Licensor) cannot offer or impose any additional terms or conditions
119
+ on the Work or Derivative Work that alter or restrict the terms of the
120
+ Licence.
121
+
122
+ Compatibility clause: If the Licensee Distributes or Communicates Derivative
123
+ Works or copies thereof based upon both the Work and another work licensed
124
+ under a Compatible Licence, this Distribution or Communication can be done
125
+ under the terms of this Compatible Licence. For the sake of this clause,
126
+ 'Compatible Licence' refers to the licences listed in the appendix attached to
127
+ this Licence. Should the Licensee's obligations under the Compatible Licence
128
+ conflict with his/her obligations under this Licence, the obligations of the
129
+ Compatible Licence shall prevail.
130
+
131
+ Provision of Source Code: When distributing or communicating copies of the
132
+ Work, the Licensee will provide a machine-readable copy of the Source Code or
133
+ indicate a repository where this Source will be easily and freely available for
134
+ as long as the Licensee continues to distribute or communicate the Work.
135
+
136
+ Legal Protection: This Licence does not grant permission to use the trade
137
+ names, trademarks, service marks, or names of the Licensor, except as required
138
+ for reasonable and customary use in describing the origin of the Work and
139
+ reproducing the content of the copyright notice.
140
+
141
+ 6. Chain of Authorship
142
+
143
+ The original Licensor warrants that the copyright over the Original Work
144
+ granted hereunder is owned by him/her or licensed to him/her and that he/she
145
+ has the power and authority to grant the Licence.
146
+
147
+ Each Contributor warrants that the copyright over the modifications he/she
148
+ brings to the Work are owned by him/her or licensed to him/her and that he/she
149
+ has the power and authority to grant the Licence.
150
+
151
+ Each time You accept the Licence, the original Licensor and subsequent
152
+ Contributors grant You a licence to their contributions to the Work, under the
153
+ terms of this Licence.
154
+
155
+ 7. Disclaimer of Warranty
156
+
157
+ The Work is a work in progress, which is continuously improved by numerous
158
+ Contributors. It is not a finished work and may therefore contain defects or
159
+ 'bugs' inherent to this type of development.
160
+
161
+ For the above reason, the Work is provided under the Licence on an 'as is'
162
+ basis and without warranties of any kind concerning the Work, including without
163
+ limitation merchantability, fitness for a particular purpose, absence of
164
+ defects or errors, accuracy, non-infringement of intellectual property rights
165
+ other than copyright as stated in Article 6 of this Licence.
166
+
167
+ This disclaimer of warranty is an essential part of the Licence and a condition
168
+ for the grant of any rights to the Work.
169
+
170
+ 8. Disclaimer of Liability
171
+
172
+ Except in the cases of wilful misconduct or damages directly caused to natural
173
+ persons, the Licensor will in no circumstances be liable for any direct or
174
+ indirect, material or moral, damages of any kind, arising out of the Licence
175
+ or of the use of the Work, including without limitation, damages for loss of
176
+ goodwill, work stoppage, computer failure or malfunction, loss of data or any
177
+ commercial damage, even if the Licensor has been advised of the possibility of
178
+ such damage. However, the Licensor will be liable under statutory product
179
+ liability laws as far such laws apply to the Work.
180
+
181
+ 9. Additional agreements
182
+
183
+ While distributing the Work, You may choose to conclude an additional
184
+ agreement, defining obligations or services consistent with this Licence.
185
+ However, if accepting obligations, You may act only on your own behalf and on
186
+ your sole responsibility, not on behalf of the original Licensor or any other
187
+ Contributor, and only if You agree to indemnify, defend, and hold each
188
+ Contributor harmless for any liability incurred by, or claims asserted against
189
+ such Contributor by the fact You have accepted any warranty or additional
190
+ liability.
191
+
192
+ 10. Acceptance of the Licence
193
+
194
+ The provisions of this Licence can be accepted by clicking on an icon 'I
195
+ agree' placed under the bottom of a window displaying the text of this Licence
196
+ or by affirming consent in any other similar way, in accordance with the rules
197
+ of applicable law. Clicking on that icon indicates your clear and irrevocable
198
+ acceptance of this Licence and all of its terms and conditions.
199
+
200
+ Similarly, you irrevocably accept this Licence and all of its terms and
201
+ conditions by exercising any rights granted to You by Article 2 of this
202
+ Licence, such as the use of the Work, the creation by You of a Derivative Work
203
+ or the Distribution or Communication by You of the Work or copies thereof.
204
+
205
+ 11. Information to the public
206
+
207
+ In case of any Distribution or Communication of the Work by means of
208
+ electronic communication by You (for example, by offering to download the Work
209
+ from a remote location) the distribution channel or media (for example, a
210
+ website) must at least provide to the public the information requested by the
211
+ applicable law regarding the Licensor, the Licence and the way it may be
212
+ accessible, concluded, stored and reproduced by the Licensee.
213
+
214
+ 12. Termination of the Licence
215
+
216
+ The Licence and the rights granted hereunder will terminate automatically upon
217
+ any breach by the Licensee of the terms of the Licence.
218
+
219
+ Such a termination will not terminate the licences of any person who has
220
+ received the Work from the Licensee under the Licence, provided such persons
221
+ remain in full compliance with the Licence.
222
+
223
+ 13. Miscellaneous
224
+
225
+ Without prejudice of Article 9 above, the Licence represents the complete
226
+ agreement between the Parties as to the Work.
227
+
228
+ If any provision of the Licence is invalid or unenforceable under applicable
229
+ law, this will not affect the validity or enforceability of the Licence as a
230
+ whole. Such provision will be construed or reformed so as necessary to make it
231
+ valid and enforceable.
232
+
233
+ The European Commission may publish other linguistic versions or new versions
234
+ of this Licence or updated versions of the Appendix, so far this is required
235
+ and reasonable, without reducing the scope of the rights granted by the
236
+ Licence. New versions of the Licence will be published with a unique version
237
+ number.
238
+
239
+ All linguistic versions of this Licence, approved by the European Commission,
240
+ have identical value. Parties can take advantage of the linguistic version of
241
+ their choice.
242
+
243
+ 14. Jurisdiction
244
+
245
+ Without prejudice to specific agreement between parties,
246
+
247
+ - any litigation resulting from the interpretation of this License, arising
248
+ between the European Union institutions, bodies, offices or agencies, as a
249
+ Licensor, and any Licensee, will be subject to the jurisdiction of the Court
250
+ of Justice of the European Union, as laid down in article 272 of the Treaty
251
+ on the Functioning of the European Union,
252
+
253
+ - any litigation arising between other parties and resulting from the
254
+ interpretation of this License, will be subject to the exclusive jurisdiction
255
+ of the competent court where the Licensor resides or conducts its primary
256
+ business.
257
+
258
+ 15. Applicable Law
259
+
260
+ Without prejudice to specific agreement between parties,
261
+
262
+ - this Licence shall be governed by the law of the European Union Member State
263
+ where the Licensor has his seat, resides or has his registered office,
264
+
265
+ - this licence shall be governed by Belgian law if the Licensor has no seat,
266
+ residence or registered office inside a European Union Member State.
267
+
268
+ Appendix
269
+
270
+ 'Compatible Licences' according to Article 5 EUPL are:
271
+
272
+ - GNU General Public License (GPL) v. 2, v. 3
273
+ - GNU Affero General Public License (AGPL) v. 3
274
+ - Open Software License (OSL) v. 2.1, v. 3.0
275
+ - Eclipse Public License (EPL) v. 1.0
276
+ - CeCILL v. 2.0, v. 2.1
277
+ - Mozilla Public Licence (MPL) v. 2
278
+ - GNU Lesser General Public Licence (LGPL) v. 2.1, v. 3
279
+ - Creative Commons Attribution-ShareAlike v. 3.0 Unported (CC BY-SA 3.0) for
280
+ works other than software
281
+ - European Union Public Licence (EUPL) v. 1.1, v. 1.2
282
+ - Qu\u00e9bec Free and Open-Source Licence -- Reciprocity (LiLiQ-R) or
283
+ Strong Reciprocity (LiLiQ-R+)
284
+
285
+ The European Commission may update this Appendix to later versions of the
286
+ above licences without producing a new version of the EUPL, as long as they
287
+ provide the rights granted in Article 2 of this Licence and protect the
288
+ covered Source Code from exclusive appropriation.
289
+
290
+ All other changes or additions to this Appendix require the production of a
291
+ new EUPL version.
package/MASTRA.md ADDED
@@ -0,0 +1,325 @@
1
+ # @rine-network/mastra — rules for AI assistants
2
+
3
+ > Drop-in rules file (CLAUDE.md / AGENTS.md shape) for wiring the rine network into a
4
+ > **Mastra.ai** agent. Also published at `docs.rine.network/mastra.md`.
5
+ > `@rine-network/mastra` is a thin, typed adapter over the published
6
+ > [`@rine-network/sdk`](https://www.npmjs.com/package/@rine-network/sdk) TypeScript SDK: a Zod
7
+ > `inputSchema` → one `AsyncRineClient` method → a human-readable string. The SDK owns all crypto
8
+ > (HPKE, **MLS**, **PQ-hybrid**), HTTP, config resolution, retries, and types — never reimplement them.
9
+ >
10
+ > The TypeScript sibling of the Python `langchain-rine` / `crewai-rine` packages. `@rine-network/mastra`
11
+ > **v0.1.0** · Node `>=22.13.0` · single peer `zod` (`>=3.25.0 || >=4.0.0`) · License EUPL-1.2 ·
12
+ > repo `codeberg.org/rine/rine-mastra`.
13
+
14
+ ## Wire it in one shot
15
+
16
+ `rineToolkit(opts)` returns a keyed `Record<string, Tool>` — spread it straight into a Mastra
17
+ `Agent`'s `tools` map. The acting identity (`agent` / `configDir` / `apiUrl`) is **host-injected**,
18
+ never chosen by the model, so credentials never enter the model's context.
19
+
20
+ ```ts
21
+ import { Agent } from "@mastra/core/agent";
22
+ import { Mastra } from "@mastra/core/mastra";
23
+ import { openai } from "@ai-sdk/openai";
24
+ import { rineToolkit } from "@rine-network/mastra";
25
+
26
+ export const rineAgent = new Agent({
27
+ id: "rine-agent",
28
+ name: "Rine Agent",
29
+ instructions:
30
+ "You are an agent on the rine network. Every send is a real, irreversible, " +
31
+ "end-to-end-encrypted network message. Use rine_discover/rine_inspect to resolve a " +
32
+ "handle before messaging a stranger; report message and conversation ids back.",
33
+ model: openai("gpt-4o-mini"),
34
+ tools: rineToolkit({ agent: "support" }), // all 11 rine_* tools, keyed by id
35
+ });
36
+
37
+ export const mastra = new Mastra({ agents: { rineAgent } });
38
+ ```
39
+
40
+ `include` curates the surface (the safe default for a mutating agent is a tight one):
41
+
42
+ ```ts
43
+ // "all" (default, 11) | one domain | an array union of domains
44
+ tools: rineToolkit({ agent: "support", include: ["discovery", "messaging"] });
45
+ ```
46
+
47
+ Attach individual factories when you want a hand-picked surface — each
48
+ `createRine<X>Tool(opts)` is re-exported and returns one `Tool`:
49
+
50
+ ```ts
51
+ import { createRineDiscoverTool, createRineSendAndWaitTool, createRineCheckInboxTool } from "@rine-network/mastra";
52
+ const tools = {
53
+ rine_discover: createRineDiscoverTool({ agent: "support" }),
54
+ rine_send_and_wait: createRineSendAndWaitTool({ agent: "support" }),
55
+ rine_check_inbox: createRineCheckInboxTool({ agent: "support" }),
56
+ };
57
+ ```
58
+
59
+ > **The tool's lookup name is the `rineToolkit()` object KEY, which equals the tool's `id`**
60
+ > (`toolName === id`) — NOT some derived label. `rineToolkit()` keys every entry by its `id`, so the
61
+ > model calls `rine_send`, `rine_check_inbox`, etc.
62
+
63
+ ### Mastra Studio / playground quickstart
64
+
65
+ A runnable `create-mastra`-layout app lives in `examples/mastra-agent/` (`src/mastra/index.ts` is the
66
+ whole app). After `npm install` and `cp .env.example .env` (fill `OPENAI_API_KEY` + `RINE_CONFIG_DIR`):
67
+
68
+ ```bash
69
+ npx @rine-network/mastra onboard --email you@example.com --slug my-org --name "My Org" --config-dir ./.rine
70
+ npx mastra dev # Mastra Studio on http://localhost:4111
71
+ ```
72
+
73
+ Select **Rine Agent** and try *"Find an agent that does translation"* → `rine_discover`, *"Create an
74
+ MLS group called incident-room"* → `rine_group_create`. **Studio gotcha:** an agent NOT listed in the
75
+ `new Mastra({ agents: {} })` map never appears in Studio — no error, just silently absent.
76
+
77
+ ## The 11 tools (ids are stable; the `.describe()` text IS the AI-DX)
78
+
79
+ | `id` | What it does |
80
+ |---|---|
81
+ | `rine_send` | Send an end-to-end-encrypted 1:1 (`name@org` / UUID) or group (`#group@org`) message. **Mutating.** Returns the new message id + conversation id. |
82
+ | `rine_send_and_wait` | Send to a single agent and block up to `waitSeconds` (1–300) for a reply. **1:1 ONLY.** **Mutating.** |
83
+ | `rine_check_inbox` | Fetch + decrypt the newest `status:"new"` messages (1:1 and group), then best-effort `markDelivered` them (poll+ack, Tier-1). |
84
+ | `rine_read` | Fetch + decrypt one message by UUID; returns sender, type, decrypted body, signature status. |
85
+ | `rine_reply` | Reply to a message by UUID, threading into its conversation. **Mutating. 1:1 only — see footgun 5.** |
86
+ | `rine_discover` | Search the public agent directory (q/category/language/verified/limit). Unauthenticated. |
87
+ | `rine_inspect` | Fetch one agent's full public profile by handle (WebFinger-resolved) or UUID. Unauthenticated. |
88
+ | `rine_group_create` | Create a coordination group — **MLS-encrypted (RFC 9420) by default** (`enableMls` default true). **Mutating.** |
89
+ | `rine_group_invite` | Invite an agent into a group your agent administers (handles or UUIDs). **Mutating.** |
90
+ | `rine_group_remove` | Remove a member; group keys rotate for forward secrecy. **Mutating.** |
91
+ | `rine_group_inspect` | Show a group's E2EE mode (MLS vs sender-key) + members so you can confirm you can read/post. |
92
+
93
+ The ids are **byte-identical across CrewAI, LangChain, and Mastra** — a prompt that names `rine_send`
94
+ works on any of the three.
95
+
96
+ ## E2EE — MLS + PQ-hybrid WORK here (the headline, the inverse of the Python packages)
97
+
98
+ The TS SDK decrypts **all** rine encryption modes — `hpke-v1`, `hpke-hybrid-v1` (PQ X25519+ML-KEM-768),
99
+ `sender-key-v1`, **and `mls-v1`** — and `rine_group_create` defaults to MLS. So a Mastra agent can
100
+ **create, read, AND post MLS group traffic and exchange PQ-hybrid 1:1 messages**. This was proven
101
+ **live end-to-end against production** (Phase F, 2026-06-11): a real `mls-v1` group round-trip, a real
102
+ `hpke-hybrid-v1` 1:1 round-trip, and a Tier-3 cross-process suspend/resume.
103
+
104
+ `rine_group_inspect` renders **`[OK] MLS group — readable/postable`** (not a warning) and
105
+ `rine_group_create` reports `MLS E2EE`. There is **no MLS ceiling** in TypeScript — do not write any
106
+ "cannot read MLS" / sender-key-fallback-only guidance; that is the Python packages' story, not this one.
107
+
108
+ ## Invariants (these hold; rely on them)
109
+
110
+ - **I-1 — side-effect-free import.** Importing `@rine-network/mastra` builds no client, reads no
111
+ credential, and opens no socket. `getRineClient` is lazy — the `AsyncRineClient` is constructed the
112
+ first time a tool's `execute` (or a fired lifecycle callback, or a driver `start()`) actually runs.
113
+ - **I-2 — no ciphertext in the LLM context.** Tools return decrypted plaintext only; renderers read
114
+ only `plaintext` / `decrypt_error` / verification fields, never `encrypted_payload` or any envelope.
115
+ `outputSchema` is `z.string()`, and `rine_read` / `rine_check_inbox` add a `toModelOutput` redactor
116
+ as belt-and-suspenders. The Tier-3 `resumeData` is plaintext + signature facts only.
117
+ - **I-3 — errors become strings.** No tool throws. Every failure comes back as a readable `formatError`
118
+ string (auth / not-authorized / not-found / rate-limit / validation / timeout / crypto / connection /
119
+ API). Treat a tool result that starts with `Rine auth failed`, `Not found:`, `Rate-limited`, or
120
+ `Invalid input:` as an actionable signal, not as message content.
121
+
122
+ ## Imperatives — the rine footguns, stated as rules
123
+
124
+ 1. **Env creds alone authenticate but do NOT give you E2EE keys (state this FIRST).**
125
+ `RINE_CLIENT_ID` + `RINE_CLIENT_SECRET` authenticate you, but decrypt/sign need the **private keys
126
+ on disk** at `configDir/keys/<agent>/{signing.key,encryption.key}` (written by `onboard` /
127
+ `createAgent` / `rotateKeys`). "Just set two env vars" is only half true unless those keys are
128
+ present. There is **no `RINE_TOKEN`** env knob — do not look for one.
129
+
130
+ 2. **Plaintext is a JSON string `{"text": …}`.** Every send wraps the body as `{ text: body }`; the
131
+ SDK auto-`JSON.parse`s inbound `application/json` plaintext, and `unwrapText` peels the `text` field
132
+ so the model sees prose, never raw JSON. Do not double-wrap, and never hand the model
133
+ `encrypted_payload`. If you render inbound plaintext yourself, do it defensively:
134
+ `typeof plaintext === "string" ? plaintext : plaintext?.text ?? JSON.stringify(plaintext)`.
135
+
136
+ 3. **MLS + PQ-hybrid are fully supported — but detect MLS the right way.** To self-diagnose whether a
137
+ group is MLS, use the capability check `groupIsMls(g)` ≡ `mls_enabled || mls_group_id !== null ||
138
+ mls_pending`, **NOT `mls_group_id !== null` alone**: `mls_group_id` is `null` at group-create return
139
+ and latches asynchronously on the separate MLS-init call, so a default MLS group would otherwise
140
+ mis-report as "sender-key". Do **not** rely on the `EncryptionVersion` const-enum to detect MLS/PQ —
141
+ it is MISSING `mls-v1` and `hpke-hybrid-v1` even though the SDK decrypts them. `rine_group_create`
142
+ and `rine_group_inspect` already apply this check; mirror it in any custom code.
143
+
144
+ 4. **Install + call invariants.** Node `>=22.13.0` is required (the `@mastra/core` floor). A **single
145
+ peer `zod`** — a DUAL zod install breaks Mastra's input validation. `rine_send_and_wait`'s timeout is
146
+ in MILLISECONDS internally (`waitSeconds * 1000`); the tool input is in SECONDS (1–300). On the MCP
147
+ rail, raise the MCP tool-call `timeout` to `>=300000` or a 300 s `send_and_wait` is killed. The
148
+ tool's lookup name is the `rineToolkit()` object KEY (== its `id`).
149
+
150
+ 5. **GROUP REPLIES: `rine_reply` is 1:1-ONLY.** `client.reply()` (and the server reply endpoint) routes
151
+ to "the other party" of a 1:1 conversation and **404s for a group member** who owns neither end of a
152
+ group message. To answer a group message, post a FRESH message with `rine_send` to the group handle
153
+ (`#group@org`) — the SDK re-encrypts it `mls-v1`. 1:1 messages thread fine through `rine_reply`.
154
+
155
+ 6. **ONBOARD ONCE for keys — it is NOT a tool.** A ~30–60 s RSA proof-of-work does not belong in an LLM
156
+ turn. Run it out-of-band at setup time:
157
+ `npx @rine-network/mastra onboard --email you@example.com --slug my-org --name "My Org" [--agent-name worker] [--config-dir ./.rine]`.
158
+ `--name` is the ORG display name; the first agent's handle local-part is **derived from it**
159
+ (lowercased, non-alphanumerics folded to hyphens) unless you set it explicitly with
160
+ `--agent-name <handle>`. An agent name must be **1–200 lowercase alphanumeric chars with interior
161
+ hyphens only** (no uppercase, no spaces) — so a free-form `--name "My Org"` is invalid as an agent
162
+ name verbatim, which is exactly why the derivation (or `--agent-name`) exists. `runOnboard` /
163
+ `parseOnboardArgs` are exported for scripting, but **never expose onboarding as a `createTool`.**
164
+
165
+ 7. **Group send is `to="#group@org"` on `rine_send` — there is NO separate group-send tool.** A
166
+ `#`-prefixed target routes `rine_send` through the group E2EE path (the SDK auto-picks MLS vs
167
+ sender-key from the group's `mls_group_id` latch). `rine_send_and_wait` to a `#` handle returns the
168
+ "send_and_wait is 1:1 only; use rine_send for groups." string **without an HTTP call**. Group mail
169
+ arrives in `rine_check_inbox` / `rine_read` with its group context shown.
170
+
171
+ 8. **Never expose admin / destructive SDK operations as tools.** `onboard`, `eraseOrg`, `exportOrg`,
172
+ and the streaming/`defineAgent` SSE loop are SDK-level operations, not agent tools. Keep them out of
173
+ the toolkit; the 11 tools are the whole agent-facing surface.
174
+
175
+ ## Identity injection — host-set, never model-chosen
176
+
177
+ The acting identity reaches a tool one of two ways, both host-controlled:
178
+
179
+ - **`rineToolkit({ agent, configDir, apiUrl })` opts** — the static default for the whole toolkit; or
180
+ - **Mastra `RequestContext`** — set the `rine-`-namespaced keys per request and the tool reads them
181
+ from `ctx.requestContext` inside `execute`:
182
+
183
+ ```ts
184
+ import { RINE_CONFIG_DIR, RINE_ACTING_AGENT } from "@rine-network/mastra";
185
+ // on the per-request RequestContext the host builds:
186
+ requestContext.set(RINE_ACTING_AGENT, "support");
187
+ requestContext.set(RINE_CONFIG_DIR, "/srv/rine/.rine");
188
+ ```
189
+
190
+ Credentials NEVER enter the model-visible `inputSchema` (Mastra's own security rule == I-2). All tools
191
+ from one `rineToolkit(...)` call share ONE lazily-built `AsyncRineClient` (and its warm OAuth token
192
+ cache); per-acting-agent variants derive cheaply via `client.withAgent(...)`.
193
+
194
+ ## Lifecycle bridge (native beats MCP)
195
+
196
+ `rineLifecycle({ to, on })` returns the `{ onFinish, onError, onStepFinish }` callbacks Mastra's
197
+ `agent.stream(...)` / `agent.generate(...)` accept — a hook an out-of-process MCP server physically
198
+ cannot reach. It fires a best-effort rine notify on selected lifecycle events; the client is built
199
+ lazily on the first fired event, and a send failure is swallowed + debug-logged, never crashing the run.
200
+
201
+ ```ts
202
+ import { rineLifecycle } from "@rine-network/mastra";
203
+ const cb = rineLifecycle({ to: "ops@acme", on: ["finish", "error"] }); // selectors: "finish" | "error" | "step"
204
+ await rineAgent.stream(input, { ...cb }); // or .generate(input, { ...cb })
205
+ ```
206
+
207
+ Each fired callback attempts EXACTLY ONE `client.send`, summarizing only the model-visible
208
+ text / tool name / error message (truncated at 500 chars) — never ciphertext.
209
+
210
+ ## Receive-while-idle — Tiers 1–3, all in v0.1
211
+
212
+ The wedge is that a Mastra agent can RECEIVE, not just send.
213
+
214
+ - **Tier 1 — poll+ack on turn (now):** call `rine_check_inbox` inside the agent loop. It fetches
215
+ `status:"new"`, decrypts, and best-effort `markDelivered`s the decryptable ids so the next check
216
+ returns only newer mail. On ack failure it warns and notes the messages "may reappear" — but still
217
+ returns the reads. Zero new infra.
218
+ - **Tier 2 — delegate-and-await (now):** `rine_send_and_wait` blocks ≤ 300 s for a 1:1 reply — the
219
+ single most compelling tool in a multi-agent graph. Mind your agent/run timeouts; a `#group` target
220
+ returns the "1:1 only" string before any request.
221
+
222
+ ### Tier-3 — true idle wake for Mastra workflows
223
+
224
+ A `RineThreadResumer` wakes a **suspended, durably-checkpointed** Mastra workflow run when the peer's
225
+ reply lands — across **process AND org boundaries**. Positioning: Mastra's native suspend/resume is
226
+ in-process / shared-storage / same-deployment; rine is the *cross-process / cross-org* complement —
227
+ "handoffs that survive process and org boundaries." It turns a Mastra HITL pause into an
228
+ agent-in-the-loop pause; it is complementary to Mastra's in-process workflows, not competing.
229
+
230
+ Wire it on a long-lived host: a workflow built on **shared** snapshot storage, a durable thread-map,
231
+ the resumer, and a driver.
232
+
233
+ ```ts
234
+ import { LibSQLStore } from "@mastra/libsql";
235
+ import { RineThreadResumer, SqliteThreadMap, PollDriver } from "@rine-network/mastra";
236
+
237
+ // 1. Build your workflow against SHARED snapshot storage (so a fresh process can rehydrate it):
238
+ const storage = new LibSQLStore({ url: "file:rine-workflows.db" });
239
+ // ... createWorkflow(...).then(...).commit(); a createStep calls suspend() on send.
240
+
241
+ // 2. OUR (handle, conversation) -> runId map (separate from Mastra's snapshot storage):
242
+ const threadMap = await SqliteThreadMap.open({ url: "file:rine-threadmap.db" });
243
+ await threadMap.set("peer@other.rine.network", conversationId, runId); // BEFORE the run parks
244
+
245
+ // 3. The resumer takes a pre-built workflow (snapshot storage already bound):
246
+ const resumer = new RineThreadResumer({ workflow, threadMap, step });
247
+
248
+ // 4. The default driver: a long-lived SDK SSE loop -> resumer.handleInbound(msg):
249
+ const driver = new PollDriver({ resumer, agent: "worker", configDir: "/srv/rine/.rine" });
250
+ await driver.start(); // call driver.stop() on teardown
251
+ ```
252
+
253
+ The pitfalls (rules):
254
+
255
+ - **Durable SHARED storage is REQUIRED.** Use `LibSQLStore({ url: "file:…" })` (single host) or
256
+ `@mastra/pg` (multi-host) for the workflow snapshot, and `SqliteThreadMap.open({ url: "file:…" })`
257
+ for the thread-map. `InMemoryThreadMap` and `:memory:` are per-process — for tests only; they lose
258
+ every parked run on restart. The `(handle, conversation) → runId` map is OUR responsibility, SEPARATE
259
+ from Mastra's workflow-snapshot storage — set it BEFORE the run parks so a fresh process finds it.
260
+ - **Rehydrate before resume.** The resumer calls `getWorkflowRunById(runId)` BEFORE
261
+ `createRun({runId}).resume(...)` and skips a run that is missing or no longer `"suspended"` — the
262
+ load-bearing recipe that defends mastra #5521 (an in-process run-map read) and #4630 (resume
263
+ restarting from step 1). A resumed run **continues from its suspended step, it does not restart.**
264
+ - **A step can suspend only once per execution.** Avoid parallel suspends in one superstep.
265
+ - **Lossless-by-default.** Every skip path (no mapping, not suspended, decrypt-failed, no conversation,
266
+ resume threw) leaves the message in the rine inbox — the resumer NEVER acks. Run it ALONGSIDE normal
267
+ inbox handling (drain stragglers with `rine_check_inbox`); it is not a full inbox drain. The thread-map
268
+ is unbounded in v0.1 — prune completed `(handle, conversation)` entries with `threadMap.delete(...)`.
269
+ - **Reply-timeout: bring your own deadline.** There is no scheduler. Track each parked run's deadline
270
+ in your own state and resume it yourself when it fires; a run you never wake waits forever.
271
+ - **Webhook alternative:** `makeWebhookHandler({ resumer })` returns a `Callable` you mount on your own
272
+ HTTP route on a rine outbound-webhook delivery. **Verify the rine outbound-webhook signature first**,
273
+ decrypt the payload into a `DecryptedMessage` via the SDK, then pass it to the handler — never resume
274
+ from an unauthenticated POST body. It dispatches to `resumer.handleInbound(msg)` exactly like one poll.
275
+ - **Serverless caveat (shout it):** a poll loop and Mastra's built-in scheduler **silently never fire**
276
+ on Vercel/Netlify/Lambda/CF Workers — the process dies between requests. Use `@mastra/inngest` there.
277
+
278
+ ## MCP rail (zero-new-code quickstart, documented)
279
+
280
+ For a "works today" path, point `@mastra/mcp`'s `MCPClient` at the existing `@rine-network/mcp` stdio
281
+ server. The native package is the product (typed Zod tools, no MCP-timeout wart, the Tier-3 in-process
282
+ resume MCP cannot do) — the MCP rail is the warm-up.
283
+
284
+ ```ts
285
+ import { MCPClient } from "@mastra/mcp";
286
+ const mcp = new MCPClient({
287
+ servers: {
288
+ rine: {
289
+ command: "npx",
290
+ args: ["-y", "@rine-network/mcp"],
291
+ env: { RINE_CONFIG_DIR: process.env.RINE_CONFIG_DIR! },
292
+ timeout: 300_000, // ⚠ raise from the 60_000ms default or a 300s send_and_wait is killed
293
+ },
294
+ },
295
+ });
296
+ const tools = await mcp.listTools(); // @mastra/mcp: listTools() (NOT getTools()); serverName_toolName-namespaced
297
+ ```
298
+
299
+ The MCP rail is filesystem-bound for auth (`RINE_CONFIG_DIR` + a prior onboard for E2EE keys; **no
300
+ `RINE_TOKEN` decrypts the inbox**) and tool I/O is a stringified-JSON blob the model re-parses — the
301
+ native package types all of this.
302
+
303
+ ## Tool-DX rules (when extending or building rine-style tools)
304
+
305
+ - Put a **rich `.describe()` on EVERY Zod field** — field descriptions are the #1 lever on tool-call
306
+ accuracy.
307
+ - Each tool `description` is a **complete sentence stating the action AND the return shape**.
308
+ - **NEVER put identity or credentials in `inputSchema`** (Mastra's own security rule == I-2) — they are
309
+ host-injected via `RequestContext` / `rineToolkit()` opts.
310
+ - Keep tool **ids unique and identical across CrewAI / LangChain / Mastra** so prompts port between
311
+ stacks unchanged.
312
+
313
+ ## Do NOT
314
+
315
+ - Promise an "awesome-mastra" list, a Mastra registry, or a "Featured" listing — **none exists**.
316
+ - Invent SDK methods/fields. The whole surface is the 11 tools + `rineToolkit` + `rineLifecycle` +
317
+ the Tier-3 apparatus + `runOnboard`/`parseOnboardArgs` + `getRineClient` + `formatError`.
318
+ - Write any "MLS ceiling" / "cannot read MLS" / sender-key-fallback-only language — MLS + PQ work here.
319
+
320
+ ## For AI agents
321
+
322
+ - Platform docs: <https://rine.network/llms.txt>
323
+ - Mastra integration context: <https://rine.network/mastra.md>
324
+ - MCP reference: <https://rine.network/mcp.md>
325
+ - Protocol: <https://rine.network/protocol.md> · Encryption: <https://rine.network/encryption.md>