eve
eve ships built-in observability: Agent Runs on Vercel and optional OpenTelemetry spans via agent/instrumentation.ts. evlog/eve adds a third layer — exportable wide events per turn with your full evlog pipeline (drains, enrichers, tail sampling, audit).
eve and evlog versions in production agents.Add evlog wide events to my eve agent
When to use what
| Need | Use |
|---|---|
| Debug a session in Vercel | eve Agent Runs (automatic) |
| Span-level traces in Datadog / Honeycomb | eve agent/instrumentation.ts + OTel exporter |
| Wide events to Axiom / Better Stack / FS, billing, audit, tail sampling | evlog/eve |
Quick Start
1. Install
pnpm add evlog eve
bun add evlog eve
yarn add evlog eve
npm install evlog eve
2. Add the hook
Create agent/hooks/evlog.ts:
import { defineEvlogHook } from 'evlog/eve'
import { createAxiomDrain } from 'evlog/axiom'
export default defineEvlogHook({
init: { env: { service: 'my-agent' } },
drain: createAxiomDrain(),
enrich: (ctx) => {
ctx.event.region = process.env.VERCEL_REGION
},
})
eve auto-discovers hook files under agent/hooks/. No HTTP middleware — the unit of work is an agent turn, not a request.
3. Log business context from tools
defineEvlogHook() binds the turn logger via AsyncLocalStorage on turn.started. Inside tool execute() handlers, call useLogger() with no arguments — same ergonomics as useLogger(event) in Nuxt or Hono.
In the example agent, support tools attach customer and order context as the agent works a refund:
import { defineTool } from 'eve/tools'
import { useLogger } from 'evlog/eve'
import { z } from 'zod'
export default defineTool({
description: 'Look up an order by id.',
inputSchema: z.object({ orderId: z.string() }),
async execute({ orderId }) {
const order = await fetchOrder(orderId)
const log = useLogger()
log.set({ order: { id: order.id, amount: order.amount, status: order.status } })
return order
},
})
useLogger() throws outside a turn, pass eve tool ctx: useLogger(ctx). This covers separate hook/tool bundles or runtimes where AsyncLocalStorage does not propagate. With a standard eve agent layout and agent/hooks/evlog.ts registered, you should not need it.4. Next.js web chat (optional)
Wrap next.config.ts with withEve() from eve/next and use useEveAgent() from eve/react in your app. eve starts alongside next dev and proxies /eve/v1/* on the same origin — tool calls, approvals, and ask_question work out of the box. See the example agent.
Wide event shape
Each completed turn emits one event:
{
"method": "EVE",
"path": "/sessions/sess_abc/turns/turn_0",
"status": 200,
"duration": "7.9s",
"service": "clearbill-support-agent",
"eve": {
"sessionId": "sess_abc",
"turnId": "turn_0",
"turnSequence": 0,
"phase": "awaiting-approval",
"sessionTurns": 1
},
"customer": { "slug": "acme-corp", "plan": "enterprise" },
"order": { "id": "4821", "amount": 890, "currency": "USD" },
"approval": { "status": "pending", "tool": "issue_refund" },
"ai": {
"calls": 2,
"steps": 2,
"inputTokens": 11724,
"outputTokens": 282,
"finishReason": "tool-calls",
"tools": [
{ "name": "lookup_customer", "durationMs": 26, "success": true },
{ "name": "lookup_order", "durationMs": 13, "success": true }
]
}
}
After approval, the next turn carries the outcome:
{
"path": "/sessions/sess_abc/turns/turn_1",
"eve": {
"sessionId": "sess_abc",
"turnId": "turn_1",
"turnSequence": 1,
"sessionTurns": 2
},
"refund": { "orderId": "4821", "amount": 890, "status": "refunded" },
"audit": { "action": "refund.issued", "target": { "type": "order", "id": "4821" } },
"approval": { "status": "approved", "tool": "issue_refund" },
"ai": {
"tools": [{ "name": "issue_refund", "durationMs": 993, "success": true }],
"finishReason": "stop"
}
}
Token usage and tool executions are accumulated from eve stream events (step.completed, actions.requested, action.result). Business fields set via useLogger() carry across turns in the same session. Link turns in analytics with eve.sessionId + eve.turnSequence — each event stays self-contained. eve.phase is only set for non-routine endings (awaiting-approval, rejected, failed).
Production
Long-running eve agents should disable terminal pretty-printing and use a non-blocking drain:
import { defineEvlogHook } from 'evlog/eve'
import { createAxiomDrain } from 'evlog/axiom'
import { createDrainPipeline } from 'evlog/pipeline'
const drain = createDrainPipeline({ batch: { size: 50, intervalMs: 5000 } })(
createAxiomDrain(),
)
export default defineEvlogHook({
init: {
env: { service: 'my-agent', environment: 'production' },
pretty: false,
sampling: { rates: { info: 10 } },
},
drain,
maxSessions: 256,
})
| Concern | Recommendation |
|---|---|
| Terminal output | init.pretty: false — pretty-print is for local dev only |
| Drain latency | Batch or async HTTP drains; never block the turn on I/O |
| Head sampling | init.sampling.rates — eve emits one event per turn, not per token |
| Memory | maxSessions (default 256) evicts oldest idle session state |
| Load | Hook handlers are O(1) per stream event; cost is dominated by your drain |
Options
| Option | Description |
|---|---|
init | Passed to initLogger() on first hook invocation |
drain / enrich / keep / plugins | Same as HTTP integrations (plugins) |
redactMessage | Default true — omits user message text from the wide event |
cost / model | Optional token pricing (ModelCost from evlog/ai) → ai.estimatedCost |
maxSessions | In-memory session cap for context carry-over (default 256) |
include / exclude | Route-style filters on turn paths (/sessions/*/turns/*) |
Tail sampling
Use keep to force-keep turns with failed tools or high token usage:
export default defineEvlogHook({
drain: createAxiomDrain(),
keep: (ctx) => {
const ai = ctx.context.ai as {
inputTokens?: number
outputTokens?: number
tools?: Array<{ success: boolean }>
} | undefined
const totalTokens = (ai?.inputTokens ?? 0) + (ai?.outputTokens ?? 0)
if (totalTokens > 10_000) ctx.shouldKeep = true
if (ai?.tools?.some(t => !t.success)) ctx.shouldKeep = true
},
})
Audit logs
Combine with Audit Logs: register auditEnricher() via init.plugins or a global plugin, and call log.audit() inside tools when a human approval gate fires. Tool rejections surface on action.result with status: "rejected".
Run locally
git clone https://github.com/HugoRCD/evlog
cd evlog
pnpm install
pnpm run example:eve
The examples/eve project is a support refund copilot (Clearbill SaaS): lookup customer → lookup order → issue refund, with eve approvals when amount > $100. Run pnpm run example:eve, open http://localhost:3000, and click a starter prompt.
What to read next
- eve hooks guide — stream event vocabulary
- eve instrumentation — OTel spans (complementary)
- AI SDK use case — when you own the model loop directly
- Adapters overview — drain destinations
Enrichers
Add derived context to every wide event automatically — user agent, geo, request size, and trace context. Built-in enrichers from evlog/enrichers, plus how to compose them with your own.
Overview
Observe what flows through the pipeline (stream, fs reader, consumer recipes), plug into the pipeline (plugins, enrichers, tail sampling, identity headers), or build your own bricks (custom drains, drain pipeline, custom framework integration).