Skip to main content

Enrich instructions

Rebuild an eve agent's system prompt per session from the authenticated Clerk caller — from a one-line greeting to the caller's full auth context.

Tip

Skip the manual setup — install the clerkInstructions() helper via shadcn.

terminal
bunx --bun shadcn@latest add clerk/eve-agents/auth

The rest of this guide builds the instructions from scratch: a minimal version first, then one that emits every attribute.

An eve agent's dynamic instructions rebuild the system prompt on every session.started event. Read the Clerk principal off ctx.session.auth.current and the prompt becomes per-caller.

A minimal dynamic instructions file

agent/instructions.ts
import { defineDynamic, defineInstructions } from 'eve/instructions'

export default defineDynamic({
  events: {
    'session.started': (_event, ctx) => {
      const auth = ctx.session.auth.current

      const sections = ['You are a helpful assistant.']

      if (auth?.principalType === 'user' && auth.attributes.name) {
        sections.push(
          `You are speaking with ${auth.attributes.name}. Address them by name when it feels natural.`,
        )
      }

      return defineInstructions({ markdown: sections.join('\n\n') })
    },
  },
})

ctx.session.auth.current is null when no caller authenticated, so guard with auth?. before reading attributes.

Inline every attribute

To reflect every attribute (org id, role, permissions, etc.), import a formatAuthAttributes helper into the instructions file and pass it the current auth context. The helper itself lives in agent/lib/utils.ts and emits one key: value line per non-empty entry.

agent/instructions.ts
import { defineDynamic, defineInstructions } from 'eve/instructions'
import { formatAuthAttributes } from './lib/utils'

export default defineDynamic({
  events: {
    'session.started': (_event, ctx) => {
      const userInfo = formatAuthAttributes(ctx.session.auth.current)

      const sections = [
        'You are a helpful assistant.',
        userInfo &&
          `Use the following info about the caller to personalize your response:\n${userInfo}`,
      ].filter(Boolean) as string[]

      return defineInstructions({ markdown: sections.join('\n\n') })
    },
  },
})
agent/lib/utils.ts
import type { SessionAuthContext } from 'eve/context'

export function formatAuthAttributes(auth: SessionAuthContext | null): string {
  if (!auth) return ''

  const lines: string[] = []
  for (const [key, value] of Object.entries(auth.attributes)) {
    const stringValue =
      typeof value === 'string'
        ? value
        : Array.isArray(value) && value.length > 0
          ? value.join(', ')
          : ''
    if (stringValue) lines.push(`${key}: ${stringValue}`)
  }

  return lines.join('\n')
}

For a signed-in user with an org membership the prompt picks up:

You are a helpful assistant.

Use the following info about the caller to personalize your response:
tokenType: session_token
orgId: org_123
role: org:admin
permissions: org:projects:archive, org:projects:read
name: Nicolas

Unauthenticated requests get an empty userInfo, so the section is filtered out.

Per-turn context from the browser

Dynamic instructions run once per session. For per-turn context, pass user info to eve's clientContext on each send.

components/chat/chat.tsx
import { useAuth, useUser } from '@clerk/nextjs'
import { useEveAgent } from 'eve/react'

export function Chat() {
  const { user } = useUser()
  const { orgId } = useAuth()

  const agent = useEveAgent({
    prepareSend: (input) => ({
      ...input,
      clientContext: {
        user: user?.fullName ?? null,
        orgId: orgId ?? null,
      },
    }),
  })
  // ...
}

Feedback

What did you think of this content?

Last updated on