Ch9. Channels, Auth, Streaming
Eve channel이 세션 생성, continuation token, route auth, NDJSON stream을 어떻게 책임지는지 분석한다.
핵심 요약
- Channel은 인증, 입력 정규화, session/continuation token 소유권, response delivery를 책임지는 front door입니다.
- route auth는 fail-closed라도 session ownership ACL은 애플리케이션이 별도로 보장해야 합니다.
- continuation token은 queue가 아니므로 플랫폼마다 ordering과 retry 정책을 channel에서 설계해야 합니다.
Channel은 Eve 에이전트의 front door입니다. 모델 품질과 tool 안전장치를 아무리 잘 설계해도 channel이 인증·세션 소유권·전달 순서를 잘못 처리하면 프로덕션 사고가 납니다.
Channel의 세 가지 책임
| 책임 | 설명 |
|---|---|
| input normalization | Slack, HTTP, GitHub 등 플랫폼 입력을 user message로 바꿈 |
| continuation token ownership | 같은 대화/스레드를 어떤 token으로 재개할지 결정 |
| delivery | agent response, approval prompt, failure를 어디에 어떻게 보낼지 결정 |
Eve 기본 HTTP channel은 POST /eve/v1/session, POST /eve/v1/session/:sessionId, GET /eve/v1/session/:sessionId/stream을 제공합니다.
기본 Eve HTTP channel
agent/channels/eve.ts를 작성하지 않아도 framework default가 활성화됩니다. 보통 이 파일은 route auth를 바꿀 때 작성합니다.
import { eveChannel } from "eve/channels/eve";
import { localDev, vercelOidc } from "eve/channels/auth";
export default eveChannel({
auth: [localDev(), vercelOidc()],
});프로덕션 browser traffic을 받으려면 앱의 세션/JWT/OIDC/API key verifier를 추가해야 합니다. localDev()와 vercelOidc()만으로는 일반 사용자를 인증하지 못합니다.
공식 Auth & Route Protection 문서 기준으로는 scaffold와 framework default를 구분해야 합니다.
| 경우 | 기본 auth | 의미 |
|---|---|---|
agent/channels/eve.ts를 삭제한 framework default | [localDev(), vercelOidc()] | localhost와 Vercel OIDC caller만 허용하며 일반 production browser traffic은 거부됩니다. |
eve init scaffold가 만든 authored channel | [localDev(), vercelOidc(), placeholderAuth()] | production browser request에 구조화된 401을 반환해 "auth 미설정"을 드러냅니다. 실제 배포 전 교체해야 합니다. |
placeholderAuth()는 임시 guardrail이지 인증 정책이 아닙니다. 공개 데모가 아니라면 none()으로 열어두지 말고 앱의 session/JWT/OIDC/API key verifier를 넣으세요.
Route auth는 fail-closed다
Eve route auth는 ordered walk입니다.
| AuthFn 결과 | 의미 |
|---|---|
SessionAuthContext 반환 | 인증 성공, walk 종료 |
null/undefined 반환 | 다음 AuthFn으로 이동 |
| throw | 401/403 등으로 거부 |
| 모두 skip | 401 |
none()을 명시하지 않으면 익명 접근은 열리지 않습니다. scaffold의 placeholderAuth()도 production에서 401을 반환합니다.
Route auth와 session ownership은 다르다
Eve 문서가 명시하는 중요한 제한이 있습니다. route auth는 HTTP boundary에서 누가 요청할 수 있는지만 판단합니다. 여러 사용자/테넌트가 같은 route에 접근할 수 있다면 session ownership ACL은 직접 구현해야 합니다.
위험한 구조:
모든 로그인 사용자 -> 같은 Eve route 접근 가능
sessionId/continuationToken만 알면 follow-up 가능
tenant ownership 검증 없음권장 구조:
| 계층 | 기준 |
|---|---|
| route auth | 사용자/서비스 인증 |
| session mapping | app DB에 sessionId -> owner/tenant/channel 저장 |
| follow-up guard | current principal이 session owner/participant인지 확인 |
| stream guard | stream endpoint도 같은 ownership 검증 |
Custom channel을 만들면 route handler에서 이 검증을 직접 넣을 수 있습니다.
Continuation token은 queue가 아니다
0.82.0 메시지는 기본 turnPolicy: "steer"로 현재 작업을 조정하고 "queue"로 active turn 종료 후 처리를 선택합니다. 같은 요청자의 steering 메시지는 durable 도착 순서를 유지하며 경계에서 합쳐질 수 있습니다. 다른 요청자의 메시지는 현재 turn이 끝날 때까지 기다립니다.
승인은 inputResponses로 requestId를 지정해 응답하는 편이 명확합니다. 승인 옵션과 맞지 않는 현재 요청자의 일반 메시지는 대기 승인을 취소하고 방향을 바꾸며, queue 설정이라도 이 특례가 적용됩니다. 이미 승인된 같은 batch의 호출은 실행될 수 있습니다. turn.waiting은 turn 종료가 아니므로 stream을 계속 읽습니다.
클라이언트는 x-eve-stream-version을 검증하고 지원하지 않는 계약을 거부해야 합니다. 현재 SDK의 정규화 동작을 사용하며 이전 append 이벤트와 현재 delta를 임의로 섞어 합산하지 않습니다.
Sessions, runs, and streaming.
NDJSON stream
GET /eve/v1/session/:sessionId/stream은 newline-delimited JSON event를 제공합니다.
중요 event:
| Event | 운영 의미 |
|---|---|
session.started | durable session 생성 |
turn.started | 새 user turn 시작 |
step.started | model step 시작 |
actions.requested | tool/subagent call 요청 |
action.result | action 결과 |
input.requested | approval/question/OAuth 등 human input 필요 |
subagent.called | child session 연결 가능 |
message.appended | assistant text delta |
message.completed | assistant message 완성 |
result.completed | structured output 완성 |
step.failed/turn.failed/session.failed | 장애 경계 |
turn.waiting | 승인·질문·인증으로 열린 turn 대기 |
session.waiting | 다음 입력 대기 |
Reasoning event를 UI/로그에 표시할 때는 privacy와 retention 정책이 필요합니다.
Event dispatch order
한 event가 발생하면 다음 순서로 처리됩니다.
- channel handler가 실행되고 adapter state를 갱신한다.
- stream에 event가 durable하게 기록된다.
- hooks가 실행된다.
- dynamic tool/skill/instruction resolver가 실행된다.
이 순서를 활용하면 channel state 기반 dynamic capability를 구현할 수 있습니다. 예를 들어 Slack channel handler가 thread metadata를 갱신하면 dynamic instruction resolver가 그 team policy를 prompt에 넣습니다.
Custom channel 구조
import { defineChannel, POST } from "eve/channels";
export default defineChannel({
routes: [
POST("/incident", async (req, args) => {
const incident = await req.json();
args.waitUntil(
args.send(`Investigate incident ${incident.id}`, {
auth: {
authenticator: "incident-service",
principalType: "service",
principalId: incident.actorId,
attributes: { incidentId: incident.id },
},
continuationToken: `incident:${incident.id}`,
}),
);
return new Response("ok");
}),
],
});Custom channel에서 반드시 검토할 것:
- HMAC/JWT/OIDC 등 raw request authentication
- body-supplied principal 신뢰 금지
- constant-time signature compare
- request IP allow-list가 필요한지
- continuation token format 안정성
- file upload fetch policy
- failure event delivery 방식
공식 channel integration 표면
공식 문서는 HTTP channel뿐 아니라 여러 platform adapter를 따로 설명합니다. 엔터프라이즈 설계에서는 "어디로 응답하는가"보다 "어떤 identity와 private delivery를 보장하는가"를 봐야 합니다.
| Channel | 공식 문서 | 운영 포인트 |
|---|---|---|
| Eve HTTP | eve | session route, stream route, auth customization |
| Custom | Custom Channels | HTTP/WS route, metadata projection, cross-channel hand-off, file staging |
| Slack | Slack | thread anchoring, ephemeral auth challenge, Connect credential setup |
| Discord | Discord | slash command, components, modal, webhook verification |
| GitHub | GitHub | GitHub App webhook, @mention, PR diff context, sandbox checkout |
| Linear | Linear | Agent Sessions, Agent Activities, native progress/question/response UX |
| Teams | Microsoft Teams | Bot Framework Activity, Adaptive Card HITL prompt |
| Telegram | Telegram | bot webhook, inline-keyboard HITL, attachment handling |
| Twilio | Twilio | SMS/phone transcription, telecom compliance review |
Custom channel에서 공식 문서가 추가로 강조하는 고급 기능은 세 가지입니다.
| 기능 | 적용 기준 |
|---|---|
WS() routes | voice, realtime collaboration, vendor SDK adapter처럼 WebSocket이 필요한 channel |
args.receive(targetChannel, ...) | incident webhook이 Slack 조사 thread를 여는 등 cross-channel hand-off가 필요한 경우 |
metadata(state) | channel state 중 dynamic resolver/instrumentation에 노출해도 되는 값만 projection |
File upload 처리
send()는 text와 file part가 섞인 UserContent를 받습니다. Slack 등 외부 파일 URL은 channel fetchFile로 안전하게 fetch/stage합니다.
위험 기준:
| 위험 | 대응 |
|---|---|
| arbitrary URL fetch | platform domain allow-list |
| 대형 파일 | size/type policy |
| 민감 파일 retention | sandbox cleanup/retention |
| prompt injection document | downstream tool/action approval |
Client SDK 사용
공식 client 문서는 raw HTTP, stream loop, browser hook을 나눕니다. script/test/server-to-server는 TypeScript SDK를 쓰고, browser UI는 frontend overview의 useEveAgent 계열을 씁니다.
import { Client } from "eve/client";
const client = new Client({
host: "https://agent.example.com",
auth: {
bearer: async () => await getAccessToken(),
},
});
const session = client.session();
const response = await session.send("Run the production checklist.");
const result = await response.result();SDK를 쓰더라도 route auth와 session ownership은 서버 설계 문제입니다.
Browser hook 설계에서 중요한 공식 기준:
| 주제 | 기준 |
|---|---|
| resumable session | sessionId, continuationToken, streamIndex가 있는 session cursor 전체를 저장합니다. |
clientContext | 다음 model call에만 붙는 ephemeral context이며 durable session history에는 남지 않습니다. |
| HITL response | input.requested가 UI message의 dynamic-tool part metadata로 투영되고 같은 session에 inputResponses로 답합니다. |
| framework integration | Next.js withEve, Nuxt eve/nuxt, SvelteKit eveSvelteKit이 같은 origin에 Eve route를 붙입니다. |
채널 운영 체크리스트
| 항목 | 기준 |
|---|---|
| route auth | production에서 placeholder 제거, fail-closed 확인 |
| session ACL | sessionId와 principal/tenant 매핑 |
| stream ACL | stream endpoint도 보호 |
| continuation | stale token 처리, 최신 token 저장 |
| ordering | per-session queue 필요 여부 |
| metadata | dynamic resolver에 노출해도 되는 값만 project |
| signatures | constant-time verify |
| eval | unauthenticated 401, wrong tenant 403, valid stream success |
채널은 단순한 transport adapter가 아니라 agent의 보안 perimeter입니다.