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 and the dashboard are both clients of the routes that start sessions, stream events, and send follow-ups. Slack gets its own webhook route, but it drives the same durable sessions underneath.
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...","deliveryId":"delivery_01..."}That's an HTTP 202 Accepted: eve took the message and started the turn in the background. 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. The deliveryId names this one message, so a client can match it to the stream later.
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.
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>/streamYou get newline-delimited JSON. Each line is a commit, {"at": ..., "facts": [...]}, holding every fact one state change produced, or a progress record carrying streamed text as it's typed. The facts tell the whole story: the message arriving, the turn starting, the lookup_service call going out and settling, the finished reply text, and the turn settling. The TUI renders this same log as the ⚙ lines you saw earlier.
The stream stays open after the turn ends, sending an occasional {"$eve":"heartbeat"} line, because the session can take more messages. Press Ctrl-C when you've seen enough.
Send a follow-up. Once the stream shows turn.settled, 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...","deliveryId":"delivery_01..."}The raw commit lines are long, so let's read only the fact types. Pipe the stream through jq (install it with your package manager if you don't have it):
curl -sN http://127.0.0.1:2000/eve/v1/session/<sessionId>/stream \
| jq -r '.facts[]?.type'delivery.admitted
session.started
delivery.consumed
turn.started
model.requested
model.started
call.requested
call.started
call.settled
usage.recorded
model.settled
model.requested
model.started
content.completed
usage.recorded
model.settled
turn.settled
delivery.settledYour list may run a little longer or shorter, since the model decides how many steps it needs. Read it top to bottom: the message is admitted, the turn starts, the model calls lookup_service (call.requested through call.settled), a second model run writes the reply (content.completed), and the turn settles. That turn.settled, with outcome: "completed", 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 pause for human approval in Section 3 and pick up days later.
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 route, or a 409 with session_not_active from the follow-up route? Check that you copied the complete sessionId from the first response. A 409 with session_not_ready means you sent the follow-up before the session's inbox finished starting. That's expected right after the first POST; wait a second and send it again.
Done-When
POST /eve/v1/sessionreturns202with asessionIdanddeliveryId.- Streaming the session prints NDJSON commit lines, including
call.requestedforlookup_serviceandturn.settled. - A follow-up
POSTto that session ID continues the conversation without repeating context. - You can explain why the first
POSTreturns 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:
| Route | What It Does |
|---|---|
POST /eve/v1/session | Start a durable session; returns 202 with a sessionId and deliveryId. |
GET /eve/v1/session/:id/stream | Stream the run as NDJSON events (reconnectable). |
POST /eve/v1/session/:id | Send 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 both drive the same durable sessions. useEveAgent calls these same routes from the browser. The Slack channel receives events on its own webhook route.
Was this helpful?