raindrop-ai 0.2.4 → 0.3.0-otelv2
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/README.md +135 -0
- package/dist/{chunk-XLEPN7ZO.mjs → chunk-5Y3LWBKV.mjs} +1033 -96
- package/dist/index-DBdUrUXc.d.mts +1457 -0
- package/dist/index-DBdUrUXc.d.ts +1457 -0
- package/dist/index.d.mts +5 -1180
- package/dist/index.d.ts +5 -1180
- package/dist/index.js +1111 -116
- package/dist/index.mjs +60 -1
- package/dist/tracing/index.d.mts +3 -1
- package/dist/tracing/index.d.ts +3 -1
- package/dist/tracing/index.js +1037 -101
- package/dist/tracing/index.mjs +1 -1
- package/package.json +9 -7
package/README.md
CHANGED
|
@@ -65,6 +65,141 @@ On runtimes that lack a usable `AsyncLocalStorage.enterWith` (notably Cloudflare
|
|
|
65
65
|
- `withSpan`/`withTool` invoke your callback as `fn.apply(thisArg, args)` — the callback receives exactly the arguments you pass (an earlier version prepended a spurious leading `undefined`). If you had code compensating for that shifted argument, remove the workaround.
|
|
66
66
|
- With per-client interaction registries, `resumeInteraction(eventId)` returns only interactions that **this** client began; a second client resuming the same event id gets its own fresh interaction rather than the first client's.
|
|
67
67
|
|
|
68
|
+
## Detached sub-agents
|
|
69
|
+
|
|
70
|
+
A **detached** sub-agent runs in another process (a queue worker, a container,
|
|
71
|
+
another machine), does not block its caller, and reports as its own Raindrop
|
|
72
|
+
event with its own lifecycle. Neither the trace context nor the event id
|
|
73
|
+
survives that boundary, so the link is carried explicitly — the caller stamps it
|
|
74
|
+
on the span it dispatched from, the child stamps the reverse reference on every
|
|
75
|
+
span it emits, and a carrier travels with the job.
|
|
76
|
+
|
|
77
|
+
**Caller** — allocate the child's event id and get the headers to send:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const supervisor = raindrop.begin({
|
|
81
|
+
eventId: turnId,
|
|
82
|
+
event: "supervisor_turn",
|
|
83
|
+
userId: "user_123",
|
|
84
|
+
convoId,
|
|
85
|
+
input: userMessage,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const dispatch = supervisor.subagent({ name: "researcher", input: { task } });
|
|
89
|
+
await queue.send({ task, headers: dispatch.headers });
|
|
90
|
+
|
|
91
|
+
// Return a handle, not an answer — the answer is the child's to report.
|
|
92
|
+
await supervisor.finish({ output: `Dispatched researcher as job ${dispatch.childEventId}.` });
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
`subagent()` allocates `childEventId` **before** dispatching, so the
|
|
96
|
+
caller's hand-off resolves while the job is still queued. It records a tool span
|
|
97
|
+
carrying the hand-off stamps — named by your `toolName`, or `launch_subagent`
|
|
98
|
+
when you pass none, so the span name is yours to choose rather than a name the
|
|
99
|
+
SDK imposes. The sub-agent's own name travels on `raindrop.handoff.name`, which
|
|
100
|
+
is where readers group by it. It returns
|
|
101
|
+
`headers` (W3C `traceparent` + `raindrop-*` baggage + `x-raindrop-handoff`),
|
|
102
|
+
`langsmithHeaders` for services that parse LangSmith's shape, and `carrier` for
|
|
103
|
+
transports that are not HTTP.
|
|
104
|
+
|
|
105
|
+
Send **all** of `headers`, not just `traceparent`. The standard pair is what other
|
|
106
|
+
readers understand, but neither header is ours to rely on: an instrumented HTTP
|
|
107
|
+
client rewrites `traceparent` from its own client span, and OTel's baggage
|
|
108
|
+
propagator *replaces* `baggage` with the caller's own entries — so a caller
|
|
109
|
+
propagating a tenant id would strip every hand-off identity and leave the child
|
|
110
|
+
unlinked. `x-raindrop-handoff` carries the same fields under a name no propagator
|
|
111
|
+
owns; it is read last and wins. A carrier header arriving **repeated with
|
|
112
|
+
differing values** is refused outright, leaving the child honestly unlinked rather
|
|
113
|
+
than letting whoever prepended a value choose the event this process reports
|
|
114
|
+
under.
|
|
115
|
+
|
|
116
|
+
With tracing disabled there is no dispatch span for a carrier to reference, so
|
|
117
|
+
`carrier` is `null` and both header bags are empty — rather than naming a span
|
|
118
|
+
that was never recorded. `childEventId` is still allocated, so a caller that
|
|
119
|
+
needs the child reporting under that id can pass it to the worker itself.
|
|
120
|
+
|
|
121
|
+
The caller never writes the child's status. It returned a job handle and moved
|
|
122
|
+
on; status is derived from the child's own event.
|
|
123
|
+
|
|
124
|
+
**Child** — resume from the carrier and report under the id the caller allocated:
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
const run = raindrop.resumeSubagent(message.headers);
|
|
128
|
+
|
|
129
|
+
try {
|
|
130
|
+
const answer = await run.interaction.withTool({ name: "search_docs" }, () =>
|
|
131
|
+
search(message.task)
|
|
132
|
+
);
|
|
133
|
+
await run.finish({ output: answer });
|
|
134
|
+
} catch (error) {
|
|
135
|
+
await run.fail(error);
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`run.interaction` is an ordinary interaction — `withSpan`, `withTool`, and
|
|
140
|
+
`trackAiEvent` all work as usual. The reverse reference (`parentEventId`,
|
|
141
|
+
`parentSpanId`, the sub-agent name, `agent.role=subagent`) is bound to the
|
|
142
|
+
execution context for the life of the run, so **every** span the child emits
|
|
143
|
+
carries it, including auto-instrumented LLM calls the SDK never sees. It is
|
|
144
|
+
unbound when the run settles.
|
|
145
|
+
|
|
146
|
+
`withSubagentRun` is the equivalent of Python's
|
|
147
|
+
`with rd.resume_subagent(...) as run:` — it guarantees an outcome is reported
|
|
148
|
+
even if the callback returns early or throws:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
await raindrop.withSubagentRun(request.headers, async (run) => {
|
|
152
|
+
await run.finish({ output: await doTheWork() });
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Endings
|
|
157
|
+
|
|
158
|
+
- `run.finish({ output })` — the normal path, same shape as `interaction.finish`.
|
|
159
|
+
- `run.fail(reason)` — errors the child's own span **and** reports the reason as
|
|
160
|
+
output. Both halves matter (see the ingest rules below). If the job died
|
|
161
|
+
before any span opened, one is recorded so there is something to mark.
|
|
162
|
+
- `run.cancel(reason)` — writes `raindrop.handoff.terminal=cancelled` and
|
|
163
|
+
reports the reason as output. It deliberately does **not** error any span:
|
|
164
|
+
error spans are what separate failed from cancelled.
|
|
165
|
+
|
|
166
|
+
### Two ingest rules worth knowing
|
|
167
|
+
|
|
168
|
+
1. **A run with no output produces no event.** A child that dies silently leaves
|
|
169
|
+
its launcher on `queued` forever — indistinguishable from a job that never
|
|
170
|
+
started. That is why `fail()` and `cancel()` both write an output, and why
|
|
171
|
+
`withSubagentRun` closes an unsettled run rather than letting it evaporate.
|
|
172
|
+
2. **Status is derived from the child's telemetry, not asserted.** A run with
|
|
173
|
+
final output and no errored span reads as *finished*, which is why `fail()`
|
|
174
|
+
must error a span and not merely say "failed" in the output. Correspondingly,
|
|
175
|
+
the dispatching turn is opted in to producing an event
|
|
176
|
+
(`raindrop.toolEvents=allow`), because a turn whose entire content is a
|
|
177
|
+
dispatch has no assistant text and is otherwise dropped as an intermediate
|
|
178
|
+
step.
|
|
179
|
+
|
|
180
|
+
### Carrier precedence and the trust boundary
|
|
181
|
+
|
|
182
|
+
The carrier wins over anything local. `resumeSubagent(headers, { convoId })`
|
|
183
|
+
treats its options as fallbacks for the **no-carrier** case, not overrides:
|
|
184
|
+
every field the carrier supplies names something the launcher already recorded.
|
|
185
|
+
A child that keeps its own convo id lands in a different conversation from the
|
|
186
|
+
supervisor that launched it, and one that keeps its own event id leaves the
|
|
187
|
+
caller's reference pointing at nothing.
|
|
188
|
+
|
|
189
|
+
`resumeSubagent` returns a usable run even when there is no carrier — a
|
|
190
|
+
sub-agent invoked directly rather than dispatched is a legitimate state, not an
|
|
191
|
+
error — so call sites stay unconditional. A malformed carrier costs the link,
|
|
192
|
+
never the request; check `run.linked` if you need to know.
|
|
193
|
+
|
|
194
|
+
> **Trust boundary.** A carrier names the event a child will be attributed to,
|
|
195
|
+
> so accepting one from an untrusted caller lets that caller write into another
|
|
196
|
+
> tenant's trace. Only read carriers that came from inside your own trust
|
|
197
|
+
> boundary.
|
|
198
|
+
|
|
199
|
+
Carriers are also readable in LangSmith's `langsmith-trace` +
|
|
200
|
+
`langsmith-metadata` shape, so a service already propagating those links with no
|
|
201
|
+
call-site change.
|
|
202
|
+
|
|
68
203
|
## Payload size limits
|
|
69
204
|
|
|
70
205
|
Text fields (ai input/output, tool span I/O, span content) are capped at
|