Vercel Logo

Drive It Over HTTP

The TUI (terminal user interface) is a comfortable place to test an agent, but no customer is ever going to open your terminal. A real dispatcher gets reached over the wire: from a web page, a Slack message, a phone. Before we wire up any of those surfaces, it's worth seeing the thing they all sit on top of.

Every eve app exposes the same HTTP session API, regardless of the interface built on top. The TUI, Slack, and dashboard are all clients of the routes that start sessions, stream events, and send follow-ups.

So let's skip the surfaces for a second and talk to the dispatcher the way every surface does, with plain curl.

Outcome

You start a durable session with the dispatcher over HTTP, stream its reply as events, and send a follow-up to the same session.

Hands-on exercise

With npx eve dev running, open a second terminal. There's nothing to write this lesson, you're learning the contract every channel speaks, so we drive it by hand.

Start a session. Send the customer's first message:

curl -X POST http://127.0.0.1:2000/eve/v1/session \
  -H 'content-type: application/json' \
  -d '{"message":"what does a basic tune-up cost?"}'

The response should look like this:

{"ok":true,"sessionId":"wrun_01...","status":"accepted"}

accepted means eve started the turn asynchronously. It is not the dispatcher's answer. Copy the sessionId from this response; you use it to stream the answer and to address the next message to this conversation.

If zsh prints % immediately after the closing brace, that is just your shell prompt touching curl's newline-free JSON output; it is not part of the response.

Accepted is a handoff, not the answer

The first request returns quickly with a session handle while the durable turn runs in the background. Seeing a response with status: "accepted" is expected. Attach to the stream with the returned sessionId to read what the dispatcher says.

Stream the session. Point at the stream route with the id from the JSON response. -N tells curl not to buffer the events:

curl -N http://127.0.0.1:2000/eve/v1/session/<sessionId>/stream

You get newline-delimited JSON, one event per line: the turn starting, the lookup_service call going out, its result coming back, the assistant's text, and the turn completing. The TUI renders this raw event log as the lines you saw earlier.

Send a follow-up. When the session is waiting for you (session.waiting), post the next message to that same session ID:

curl -X POST http://127.0.0.1:2000/eve/v1/session/<sessionId> \
  -H 'content-type: application/json' \
  -d '{"message":"and how long does it take?"}'

The dispatcher retained the session history, so it already knows you're asking about the tune-up.

Try It

The first POST returns something like this:

{ "ok": true, "sessionId": "wrun_01...", "status": "accepted" }

And the stream prints the turn as it happens:

{"type":"session.started"}
{"type":"turn.started"}
{"type":"actions.requested","data":{"calls":[{"name":"lookup_service","input":{"query":"tune-up"}}]}}
{"type":"action.result","data":{"result":[{"name":"Basic Tune-Up","price":"$65.00","estMinutes":60}]}}
{"type":"message.completed","data":{"finishReason":"stop"}}
{"type":"turn.completed"}
{"type":"session.waiting"}

That session.waiting at the end is the agent telling you it's done with this turn and ready for the next message. The same durable machinery will let a turn park for human approval in Section 3 and pick up days later.

Send one turn at a time

For predictable ordering, wait for session.waiting before sending the next message to the same session. Fire two messages at once and you're racing the runtime, which is rarely what you want in a chat.

Getting a 401? The dev server accepts local requests, but make sure you're hitting 127.0.0.1/localhost and not a deployed URL. Getting a 404 from the stream or follow-up route? Check that you copied the complete sessionId from the first response.

Done-When

  • POST /eve/v1/session returns status: "accepted" and a sessionId.
  • Streaming the session prints NDJSON events, including the lookup_service call and session.waiting.
  • A follow-up POST to that session ID continues the conversation without repeating context.
  • You can explain why the first POST returns a handle instead of the assistant's answer.

Solution

There's no code to write here. The agent exposed these routes when you ran it, and every channel uses the same contract:

RouteWhat It Does
POST /eve/v1/sessionStart a durable session; returns an accepted status + sessionId.
GET /eve/v1/session/:id/streamStream the run as NDJSON events (reconnectable).
POST /eve/v1/session/:idSend a follow-up to the same durable session.

Hold onto this picture. In Section 4 you'll put a web dashboard and Slack in front of the dispatcher, and neither one invents a new way to talk to it. useEveAgent calls these same routes from the browser; the Slack channel calls them from a webhook.

Was this helpful?

supported.