Skip to content

feat(node): Add eveConversationHook() to link eve sessions as Sentry conversations - #24247

Open
mydea wants to merge 6 commits into
developfrom
feat/eve-conversation-hook
Open

feat(node): Add eveConversationHook() to link eve sessions as Sentry conversations#24247
mydea wants to merge 6 commits into
developfrom
feat/eve-conversation-hook

Conversation

@mydea

@mydea mydea commented Sep 9, 2026

Copy link
Copy Markdown
Member

Stacked on #24228 (base branch feat/e2e-eve-agent).

Adds eveConversationHook(), used as the default export of an eve agent/hooks/sentry.ts:

import * as Sentry from '@sentry/node';
import { defineHook } from 'eve/hooks';

export default defineHook(Sentry.eveConversationHook());

It tags every turn of an eve session with the durable session id (ctx.session.id) as the Sentry conversation id, so the session's AI spans — which land in separate traces, since each eve turn is its own durable workflow — group into one conversation in the Agents "Conversations" view.

Root cause / why this shape:

  • Scope, not the AI call. eve's session id never reaches the AI SDK's telemetry diagnostics channel (the channel event only carries recordInputs/recordOutputs/functionId; runtimeContext/metadata go to eve's own OTel integration, which Sentry bypasses). So the id can only be attached via the isolation scope, where the always-on conversationIdIntegration picks it up and stamps gen_ai.conversation.id onto the gen_ai spans.
  • Both turn.started and step.started. Each turn is a fresh request with its own isolation scope, and a turn that parks and resumes (approvals, compaction) resumes in another request where turn.started won't re-fire. step.started runs before every model call, so together they cover every request that produces spans. Re-setting the same id is idempotent.
  • Lives in @sentry/server-utils. The helper only needs setConversationId from core, so it belongs in the framework-agnostic shared layer, not @sentry/node. @sentry/node re-exports it (so every export * from '@sentry/node' SDK — astro, nitro (eve's own base), nestjs, hono, effect — surfaces it automatically); it is added explicitly to the node-based @sentry/bun, @sentry/aws-serverless, @sentry/google-cloud-serverless, and to the server-utils-based @sentry/deno and @sentry/cloudflare.
  • Structural typing. The eve hook context is typed structurally rather than imported from eve, so the SDK keeps no dependency on the framework; the shape is checked at the defineHook(...) call site. An optional getConversationId lets users override the default (e.g. to use a root/parent session id for subagents).

The node-eve e2e app now uses the hook and asserts gen_ai.conversation.id on each gen_ai span equals the eve session id returned by the session endpoint.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

size-limit report 📦

⚠️ Warning: Base artifact is not the latest one, because the latest workflow run is not done yet. This may lead to incorrect results. Try to re-run all tests to get up to date results.

Path Size % Change Change
@sentry/browser 28.92 kB - -
@sentry/browser - with treeshaking flags 27.21 kB - -
@sentry/browser - with treeshaking flags tracing without tracing 27.11 kB - -
@sentry/browser (incl. Tracing) 50.42 kB - -
@sentry/browser (incl. Tracing + Span Streaming) 50.43 kB - -
@sentry/browser (incl. Tracing, Profiling) 53.41 kB - -
@sentry/browser (incl. Tracing, Replay) 89.93 kB - -
@sentry/browser (incl. Tracing, Replay) - with treeshaking flags 79.05 kB - -
@sentry/browser (incl. Tracing, Replay with Canvas) 94.64 kB - -
@sentry/browser (incl. Tracing, Replay, Feedback) 107.65 kB - -
@sentry/browser (incl. Feedback) 46.41 kB - -
@sentry/browser (incl. sendFeedback) 33.97 kB - -
@sentry/browser (incl. FeedbackAsync) 39.08 kB - -
@sentry/browser (incl. Metrics) 29.93 kB - -
@sentry/browser (incl. Logs) 30.2 kB - -
@sentry/browser (incl. Metrics & Logs) 30.87 kB - -
@sentry/react 30.67 kB - -
@sentry/react (incl. Tracing) 52.73 kB - -
@sentry/vue 36.16 kB - -
@sentry/vue (incl. Tracing) 52.66 kB - -
@sentry/svelte 28.93 kB - -
CDN Bundle 30.66 kB - -
CDN Bundle (incl. Tracing) 50.93 kB - -
CDN Bundle (incl. Logs, Metrics) 32.93 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) 52.91 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) 73.62 kB - -
CDN Bundle (incl. Tracing, Replay) 88.48 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) 90.43 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) 94.55 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) 96.55 kB - -
CDN Bundle - uncompressed 90.73 kB - -
CDN Bundle (incl. Tracing) - uncompressed 152.17 kB - -
CDN Bundle (incl. Logs, Metrics) - uncompressed 97.31 kB - -
CDN Bundle (incl. Tracing, Logs, Metrics) - uncompressed 158.13 kB - -
CDN Bundle (incl. Replay, Logs, Metrics) - uncompressed 226.72 kB - -
CDN Bundle (incl. Tracing, Replay) - uncompressed 271.74 kB - -
CDN Bundle (incl. Tracing, Replay, Logs, Metrics) - uncompressed 277.69 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback) - uncompressed 285.44 kB - -
CDN Bundle (incl. Tracing, Replay, Feedback, Logs, Metrics) - uncompressed 291.38 kB - -
@sentry/nextjs (client) 55.05 kB - -
@sentry/sveltekit (client) 50.83 kB - -
@sentry/core/server 37.08 kB - -
@sentry/core/browser 13.66 kB - -
@sentry/node 129.77 kB +0.02% +20 B 🔺
@sentry/node/import (ESM hook with diagnostics-channel injection) 81.83 kB - -
@sentry/node - without tracing 89.57 kB +0.04% +30 B 🔺
@sentry/node - without channel injection 108.65 kB +0.03% +22 B 🔺
@sentry/aws-serverless 97.79 kB +0.02% +19 B 🔺
@sentry/cloudflare (withSentry) - minified 203.02 kB - -
@sentry/cloudflare (withSentry) 505.43 kB - -

View base workflow run

@mydea
mydea added this pull request to stack #24250 September 9, 2026 13:29
@mydea
mydea marked this pull request as ready for review September 9, 2026 14:43
@mydea
mydea requested review from a team as code owners September 9, 2026 14:43
@mydea
mydea requested review from JPeer264 and isaacs and removed request for a team September 9, 2026 14:43
@mydea
mydea requested a review from a team as a code owner September 9, 2026 14:51
@mydea
mydea requested review from nicohrubec and s1gr1d and removed request for a team September 9, 2026 14:51
@chargome
chargome self-requested a review September 10, 2026 09:40

@RulaKhaled RulaKhaled left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left two comments, otherwise lgtm

Comment thread packages/server-utils/src/eve.ts Outdated

const setConversationIdFromContext: EveHookHandler = (_event, context) => {
const conversationId = resolveConversationId(context);
if (conversationId) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this mean a resolver returning undefined cannot unset the conversation id? setConversationId's documentsnull | undefined

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, yeah, should not be that way, I refactored this to just use the value as-is and set it!

Comment thread packages/deno/src/index.ts
*
* ```ts
* // agent/hooks/sentry.ts
* import * as Sentry from '@sentry/node';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I guess this can also come from non-node packages?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jup, theoretically, but eve is mostly just documented for node, so I think it's fine to keep this as example/docs here?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree to keep it for node only for now. I don't think Eve would be optimized too much for other runtimes - if we stay with Node for now we have the minimal setup working it's a great start

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4bf7875. Configure here.

Comment thread packages/server-utils/src/eve.ts
Comment thread packages/server-utils/src/eve.ts

@JPeer264 JPeer264 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Great points were already raised before.

@mydea
mydea force-pushed the feat/eve-conversation-hook branch from 6110680 to 796048f Compare September 11, 2026 08:03
Base automatically changed from feat/e2e-eve-agent to develop September 11, 2026 10:40
mydea and others added 6 commits September 11, 2026 12:41
…y conversations

Adds `Sentry.eveConversationHook()`, used as the default export of an eve
`agent/hooks/sentry.ts`:

    export default defineHook(Sentry.eveConversationHook());

It tags every turn of an eve session with the durable session id as the Sentry
conversation id, so the session's AI spans — which land in separate traces
(each eve turn is its own durable workflow) — group into one conversation in
the Agents "Conversations" view.

The id is set on the isolation scope rather than on the AI call: eve's session
id never reaches the AI SDK's telemetry diagnostics channel, so the only way to
attach it is via the scope, where the always-on `conversationIdIntegration`
picks it up and stamps `gen_ai.conversation.id` onto the gen_ai spans.

Subscribes to both `turn.started` and `step.started`. Each turn is a fresh
request with its own isolation scope, and a turn that parks and resumes
(approvals, compaction) resumes in another request where `turn.started` won't
re-fire — `step.started` runs before every model call, so together they cover
each request that produces spans. Re-setting the same id is idempotent.

The eve hook context is typed structurally (not imported from `eve`) so
`@sentry/node` keeps no dependency on the framework; the shape is checked at the
`defineHook(...)` call site instead.

The node-eve e2e app uses the hook and asserts `gen_ai.conversation.id` on each
gen_ai span equals the eve session id.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e-export from every runtime SDK

The helper only needs `setConversationId` from core, so `@sentry/server-utils`
is its natural home — a framework-agnostic shared layer already re-exported by
the runtime SDKs — rather than living in `@sentry/node`.

- `@sentry/node` now re-exports it from `@sentry/server-utils` (like the other
  shared server helpers), so every node-based SDK that does
  `export * from '@sentry/node'` (astro, nitro — eve's own base —, nestjs,
  hono, effect) surfaces it automatically.
- Added to the explicit `@sentry/node` re-export blocks of `@sentry/bun`,
  `@sentry/aws-serverless` and `@sentry/google-cloud-serverless`.
- Added to the `@sentry/server-utils` re-export blocks of `@sentry/deno` and
  `@sentry/cloudflare`, which build on server-utils rather than node.

The unit test moves alongside the implementation into `@sentry/server-utils`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
It is a single-field options bag callers pass as an inline object literal, so
exporting the type name added public API surface across every runtime SDK for
no benefit. It stays declared (unexported) alongside the function, so the
signature is unaffected and callers still pass options structurally.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Astro's runtime entry curates its `@sentry/node` re-exports (it can't
`export *`), so the helper has to be listed explicitly like the other SDKs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mydea
mydea force-pushed the feat/eve-conversation-hook branch from 796048f to af51063 Compare September 11, 2026 10:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants