SDK
Vercel eve
stillrunning-eve is a single eve hook. Drop it in and every run your agent does, scheduled or triggered by a user, reports to StillRunning with its duration, tokens, cost, model, and tool calls. A conversation groups into one replayable trace, failures alert with the error text, and the hook is built so it can never fail the run it is monitoring.
Install
npm install stillrunning-eve30-second quickstart
1. Create a monitor at stillrunning.ai/app/new and copy its ping token.
2. Set it as an environment variable:
STILLRUNNING_TOKEN=your_token_here3. Add one file to your agent:
import { stillrunning } from "stillrunning-eve";
export default stillrunning(); // reads STILLRUNNING_TOKENThat is the whole integration. eve discovers the hook, and every turn your agent runs now shows up on your dashboard.
Agents that run when your users show up
StillRunning was built around absence: it knows when a job that should have run didn't. That is the right model for a cron agent and the wrong one for a chat agent, which is silent whenever nobody is talking to it. Monitoring a support bot against a schedule just teaches you to ignore the alerts.
So pick on demandwhen you create the monitor. It has no expected interval, the sweeper skips it entirely, and a quiet night can never mark it down. Everything else is unchanged: every run still records duration, tokens, cost and model, failures still alert, cost and duration anomalies still fire against the monitor's own baseline, and monthly budgets still apply.
| Your agent | Monitor mode |
|---|---|
scheduled | Runs on a cadence. A missed run is a failure worth waking up for. |
on_demand | Runs when triggered: chat, webhook, API. Silence is normal, so only real failures alert. |
Reach for on_demandonly when there is genuinely no expected cadence. If a job ought to run nightly and simply hasn't yet, it is scheduled, and choosing on-demand there throws away the absence detection that is the main reason to monitor it.
What gets reported
The hook maps eve's session lifecycle onto StillRunning runs:
| eve event | What the hook does |
|---|---|
session.started | Captures the model id from the runtime identity, so every turn can report it. |
turn.started | Opens a run and sends a start ping. |
step.completed | Accumulates input/output tokens and cost from eve’s per-step usage. |
action.result | Counts a tool call. |
message.received / message.completed | Records the question and the reply (see below). |
turn.completed | Sends a success ping with duration, tokens, cost, model, and tool calls. |
turn.failed / session.failed | Sends a fail ping with the error message. |
Cost comes from eve's own costUsd, computed per step through AI Gateway, so it is the real figure rather than an estimate from a pricing table.
Turns, sessions, and Replay
An eve session is a conversation, and a conversation holds many turns. The hook models those as separate things, which is what makes Replay useful:
| Ping field | eve concept |
|---|---|
rid | The turn. One run = one question and its answer, with its own duration and token count. |
traceId | The session. Every turn in the conversation shares it. |
Because the turns share a trace, StillRunning stitches them into a single outcome. Open it and you get the whole conversation in order, each turn with what was asked, what came back, how long it took and what it cost, rather than a pile of disconnected runs.
Recording the conversation
Each run's outputcarries the user's message and the agent's reply, which is what the dashboard renders in the run list and the timeline:
What happens if my agent stops running? → That is exactly the failure
StillRunning is built for. If your agent stops producing events, most
monitoring stays silent too, since there is nothing to catch.This is on by default, because a run row that shows what was asked and what came back is most of what makes an agent's history worth reading. Long messages are truncated, never dropped. To turn it off, for agents handling anything you would rather not send to a third party:
export default stillrunning({ captureText: false });With it off, no conversation text leaves your process, and every metric still reports. Disabling text is not disabling monitoring.
Filtering by channel
By default every run is reported. To narrow that, filter on eve's channel kind, which is schedule for cron-triggered runs, http, subagent, or channel:<name> for a channel you defined:
stillrunning({ channels: ["schedule"] }); // only scheduled runs
stillrunning({ excludeChannels: ["schedule"] }); // everything except scheduled runsexcludeChannelswins when both are set. Prefer it when you want "everything a user triggers", since an allowlist silently stops reporting any channel kind you add later.
Optional: splitting liveness from usage
Most agents want one monitor covering everything they do. Start there. If your agent has both a cron schedule and a user-facing surface, and you want the two questions answered separately, run one hook per monitor:
// agent/hooks/liveness.ts , scheduled monitor: "is this agent still alive?"
export default stillrunning({ channels: ["schedule"] });
// agent/hooks/telemetry.ts , on-demand monitor: "how are real user runs going?"
export default stillrunning({
token: process.env.MY_SECOND_STILLRUNNING_TOKEN ?? "",
excludeChannels: ["schedule"],
});The scheduled half is what tells you the agent went dark, which an on-demand monitor by definition cannot. Two notes if you do this: the ?? "" matters, because an undefined token falls back to STILLRUNNING_TOKEN and would quietly report chat runs into your liveness monitor. And it is only worth the second monitor when the heartbeat is real and you have watched it arrive.
The never-throw guarantee
eve escalates a thrown hook to turn.failed, and a throw during a failure cascade to session.failed. A monitoring hook that throws would therefore fail the very run it exists to observe. This one is built so that cannot happen:
- Every handler swallows every error, including bugs in your own
onErrorcallback. - Pings race a hard timeout (
pingTimeoutMs, default 3000), so a hung network call cannot stall a turn. - With no token configured it warns once and no-ops instead of throwing.
The worst case is a missed ping. Monitoring never breaks the thing it monitors.
Configuration
stillrunning({
token, // ping token; defaults to process.env.STILLRUNNING_TOKEN
baseUrl, // defaults to https://stillrunning.ai
channels, // only report these channel kinds; default: all
excludeChannels, // never report these; wins over channels
captureText, // record the conversation as the run’s output; default true
pingTimeoutMs, // hard cap per ping; default 3000
onError, // (err) => void — observe ping delivery failures
fetch, // custom fetch (testing / non-global-fetch runtimes)
debug, // log lifecycle + ping results; or set STILLRUNNING_DEBUG=1
})Requirements
eve >=0.20 <1 as a peer dependency, and no other runtime dependencies. The only thing pulled from the framework is defineHook; the event handling itself never imports eve, which is why it can be unit-tested without an eve runtime.
You're set
Open your dashboard to watch turns land in real time, with cost, duration, and the full conversation behind each run.
Open dashboard